<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rémy Hannequin</title>
    <description>The latest articles on DEV Community by Rémy Hannequin (@rhannequin).</description>
    <link>https://dev.to/rhannequin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F778511%2F9e679466-0ab3-4951-ba59-e4d083d1d596.png</url>
      <title>DEV Community: Rémy Hannequin</title>
      <link>https://dev.to/rhannequin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rhannequin"/>
    <language>en</language>
    <item>
      <title>Introducing Caelus</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Fri, 31 Oct 2025 10:05:32 +0000</pubDate>
      <link>https://dev.to/rhannequin/introducing-caelus-569h</link>
      <guid>https://dev.to/rhannequin/introducing-caelus-569h</guid>
      <description>&lt;p&gt;It's not often you finally build something you've been dreaming about for more than a decade.&lt;br&gt;
For me, that moment came today.&lt;/p&gt;

&lt;p&gt;While what I'm presenting today is far from complete and still only offers a few features, the way it is built is an achievement I have been dreaming of for many years.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to compute astronomical data
&lt;/h2&gt;

&lt;p&gt;You open your favourite weather app. It tells you the Sun will rise at 07:34 today, and you trust it. There's no reason not to. But how does it know? Then, you change your location to see how the weather is in another part of the country, and you realise the sunset time is different. Finally, you also realise the app tells you what is the current Moon phase, and when the next full Moon will happen. How does it know what will happen tonight, tomorrow, a year from now?&lt;/p&gt;

&lt;p&gt;I wasn't satisfied with simply coding and manipulating astronomical data. I wanted a deeper understanding of the entire process, from how the data is gathered to how the sky fundamentally operates.&lt;/p&gt;

&lt;p&gt;I believe astronomical data is universal, pun intended. If scientists managed to understand how the sky works, then this knowledge should be accessible to everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Astronoby
&lt;/h2&gt;

&lt;p&gt;Four years ago, I started working on &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;Astronoby&lt;/a&gt;, initially basing my work on many astronomy and astrometry books. More recently, Astronoby was refactored to use raw position data of Solar System objects directly from the best sources available: IMCCE and NASA. The former provides astronomical data for EU countries, while the latter is the US space agency.&lt;/p&gt;

&lt;p&gt;Astronoby is open source, so all the code and logic to transform the data, all the algorithms to compute event times, everything is accessible for anyone to understand how it works and spot any mistakes I might have made. It is precise enough to enable any amateur telescope to target planets for the next 50 years, accurate to the second.&lt;/p&gt;

&lt;p&gt;Finally, with access to accurate astronomical data based on logic that is open to anyone, I can finally tackle my decade-long goal: build my own astronomical tool and make it accessible to the public. This is Caelus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caelus
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://caelus.siderealcode.net" rel="noopener noreferrer"&gt;Caelus&lt;/a&gt; is heavily inspired by the amazing &lt;a href="https://www.heavens-above.com" rel="noopener noreferrer"&gt;Heavens Above&lt;/a&gt;. Aside from the fact that Heavens Above currently provides many more features, the main difference is that everything in Caelus is traceable.&lt;/p&gt;

&lt;p&gt;If Caelus shows a sunrise time of 07:34, you can trace back to the source data and understand the entire chain of logic that produced it. This includes the algorithm calculating the Sun's position, the code handling the observer's location, and even adjustments for atmospheric effects. Every single piece of data can be checked by the user, making it truly universal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's now, what's next
&lt;/h2&gt;

&lt;p&gt;Currently Caelus offers three main webpages that provide most of the general information about tonight for the observer.&lt;/p&gt;

&lt;p&gt;You will see information about the Moon&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggy2y76jvqk0kx7xkztp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggy2y76jvqk0kx7xkztp.png" alt="Caelus screenshot: Moon tonight" width="748" height="1080"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dedicated information about the Sun&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtxeqh7k7uxwfo4p231z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtxeqh7k7uxwfo4p231z.png" alt="Caelus screenshot: Distance of the Sun and seasons times" width="748" height="1518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Information about the visibility of planets&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdv88hwvgjsvhrdvg139p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdv88hwvgjsvhrdvg139p.png" alt="Caelus screenshot: Planets tonight" width="780" height="1552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Accessible deep-sky objects visible from your location&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1odsmeotgcixet3amchn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1odsmeotgcixet3amchn.png" alt="Caelus screenshot: Deep-sky objects visible tonight" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two main things I want to focus on now are the documentation and an observation planner.&lt;/p&gt;

&lt;p&gt;The first is creating &lt;strong&gt;educational content&lt;/strong&gt; that explains the astronomy behind the data. This will include definitions for common terms, explanations of physical effects (like atmospheric refraction), and a breakdown of the calculations. If the user doesn't know how to read Ruby code but still wants to learn more about how we compute this data and what it means, I want Caelus to help.&lt;/p&gt;

&lt;p&gt;The observation planner will be a way to specify what you're interested in, when you're going to observe the sky and where you will be observing from, and Caelus will provide all the right data so that the user can plan their observation night.&lt;/p&gt;

&lt;p&gt;I'm also keen to hear from the community and try to produce whatever data people are interested in. I want Caelus to become a useful and educational open source tool.&lt;/p&gt;

&lt;p&gt;The entire project is open source and hosted on GitHub: &lt;a href="https://github.com/rhannequin/caelus" rel="noopener noreferrer"&gt;https://github.com/rhannequin/caelus&lt;/a&gt;. I welcome feedback, ideas, issues, and pull requests from anyone interested in collaborating.&lt;/p&gt;

&lt;p&gt;Cheers! 🌌&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Cover picture credits: altargods.com&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
      <category>astronomy</category>
    </item>
    <item>
      <title>How to calculate the best days for planetary observation</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Mon, 01 Sep 2025 08:14:56 +0000</pubDate>
      <link>https://dev.to/rhannequin/how-to-calculate-the-best-days-for-planetary-observation-28k</link>
      <guid>https://dev.to/rhannequin/how-to-calculate-the-best-days-for-planetary-observation-28k</guid>
      <description>&lt;p&gt;A few months ago, I started a tradition: each time I release a new version of &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;Astronoby&lt;/a&gt;, I write a blog post showing how Ruby and the amateur astronomy communities can use it to compute astronomical events.&lt;/p&gt;

&lt;p&gt;Today, to celebrate &lt;a href="https://github.com/rhannequin/astronoby/releases/tag/v0.8.0" rel="noopener noreferrer"&gt;v0.8.0&lt;/a&gt;, we'll compute the best days for planetary observation. This illustrates Astronoby's new caching and performance improvements, and hints at features coming in future versions.&lt;/p&gt;

&lt;p&gt;But first, a bit of context.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is planetary observation?
&lt;/h2&gt;

&lt;p&gt;In amateur astronomy, when we say &lt;em&gt;planetary&lt;/em&gt;, we usually mean three types of bodies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the planets&lt;/li&gt;
&lt;li&gt;the Moon&lt;/li&gt;
&lt;li&gt;the Sun&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you jumped because the Moon and the Sun are not planets, you're right, but please hear me out.&lt;/p&gt;

&lt;p&gt;Planetary observation differs from deep sky observation. Deep sky objects are very different from planets, usually much fainter. But also they always appear at the same place in the sky at the same time of the year. They represent nebulae, galaxies, stars clusters from our own galaxy or surrounding. On a human timescale, they appear fixed in the sky. Only Earth's revolution around the Sun changes their visibility throughout the year.&lt;/p&gt;

&lt;p&gt;Planets on the other hand are very close and very bright, and they have their own motion which is noticeable on a daily basis. The Moon changes phases, while some planets like Mars is best to be observed every two years.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsstwveak9qr1ff8vuwim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsstwveak9qr1ff8vuwim.png" alt="Apparent reversal of Mars' movement in the night sky" width="800" height="533"&gt;&lt;/a&gt;&lt;small&gt;&lt;small&gt;Credits: BBC Sky at Night Magazine&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Planetary observation depends on many constantly changing factors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sun observation
&lt;/h2&gt;

&lt;p&gt;Let's start with the easiest one on paper, but also the most dangerous. The "astronomer code of conduct" requires me to remind you, even if you might already know it, to never look at the Sun with the naked eye or any equipment, unless you are using a very secure and safe one.&lt;/p&gt;

&lt;p&gt;You might think, why would we observe something that can burn the retina? Because, with a dedicated equipment, the Sun is full of great things to see like sun spots, which are cooler areas on the surface, or flares, which are ejections of coronal mass, and many more.&lt;/p&gt;

&lt;p&gt;The Sun is easy because it doesn't really move, our perspective of movement is mainly due to the Earth's rotation and revolution.&lt;/p&gt;

&lt;p&gt;The Earth's tilt makes the Sun appear more or less high in the sky across the year. If you want to know more about the why and how, check out my previous blog post on the &lt;a href="https://dev.to/rhannequin/where-on-earth-do-shadows-disappear-finding-the-sub-solar-point-with-ruby-5hm"&gt;sub-solar point&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, the best time to observe the Sun is around the summer solstice, June in the northern hemisphere, December in the southern, when it is highest in the sky and visible for the longest time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujsi2trrrbhau2d81rqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujsi2trrrbhau2d81rqo.png" alt="The Sun Analemma" width="800" height="533"&gt;&lt;/a&gt;&lt;small&gt;&lt;small&gt;Credits: Sky &amp;amp; Telescope&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;You don't need an astronomy library to know when are the few weeks around the solstice. But you might be interested to know what is the highest altitude the Sun could go as seen from your location. Let's use Astronoby to compute the day of solstice, and then compute the highest altitude of the Sun that day, when it transits. As someone living in France, I'm going to use the June solstice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Download an ephemeris if you don't have one yet.&lt;/span&gt;
&lt;span class="c1"&gt;# Check out https://github.com/rhannequin/astronoby/wiki/Ephem&lt;/span&gt;

&lt;span class="c1"&gt;# Load an ephemeris&lt;/span&gt;
&lt;span class="n"&gt;ephem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ephem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"inpop19a.bsp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Set up the observer with your GPS coordinates&lt;/span&gt;
&lt;span class="c1"&gt;# For the example: Paris, France&lt;/span&gt;
&lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;latitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;48.8575&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;longitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.3514&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Compute solstice day&lt;/span&gt;
&lt;span class="n"&gt;solstice_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EquinoxSolstice&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;june_solstice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_date&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Date: 2025-06-21 ((2460848j,0s,0n),+0s,2299161j)&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RiseTransitSetCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;observer: &lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Compute time of Sun's transit&lt;/span&gt;
&lt;span class="n"&gt;transit_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solstice_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transit_time&lt;/span&gt;

&lt;span class="c1"&gt;# Compute Sun' state at this instant&lt;/span&gt;
&lt;span class="n"&gt;sun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;instant&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transit_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Get the altitude in degrees&lt;/span&gt;
&lt;span class="n"&gt;altitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observed_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;horizontal&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;altitude&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 64.58&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paris is decently North, so the Sun gets pretty high during the solstice but not as high as observed from the Tropic of Cancer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planets observation
&lt;/h2&gt;

&lt;p&gt;Things get a bit more complicated from here. First, because the planets are constantly moving, and so does Earth, so they cannot be expected to be at the same location in the sky every year.&lt;/p&gt;

&lt;p&gt;But also, some planets have orbits closer to the Sun than Earth, and some are further, which changes the orientation of looking at them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inferior planets
&lt;/h3&gt;

&lt;p&gt;Let's start with the only two planets whose orbits lie closer to the Sun than Earth's: Mercury and Venus.&lt;/p&gt;

&lt;p&gt;From our perspective, they are always quite close to the Sun. Sometimes, when they are between the Sun and the Earth, or when the Sun is between them and the Earth, they cannot be seen at all. That's why some cultures call Venus the Morning Star: it's often visible just before sunrise (or just after sunset).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv43za05g0tytjzhmuasd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv43za05g0tytjzhmuasd.png" alt="Elongation configurations of an inferior planet" width="364" height="335"&gt;&lt;/a&gt;&lt;small&gt;&lt;small&gt;Credits: University of Nebraska-Lincoln&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;In conclusion, the best moment to observe these planets is a range of a few days or weeks around when the planet is the further from the Sun, from our perspective. This moment is called the &lt;em&gt;greatest elongation&lt;/em&gt;. It is the maximal angular distance between the planet and the Sun, as observed from Earth.&lt;/p&gt;

&lt;p&gt;We are going to use Astronoby to compute the top 30 days when Venus is the further from the Sun.&lt;/p&gt;

&lt;p&gt;Astronoby doesn't have a built-in function (yet) for this, so we're going to do it ourselves. However, it means we are going to compute Venus' position for every day of the year, this can be quite expensive.&lt;/p&gt;

&lt;p&gt;That's where I can advertise on Astronoby's latest feature: internal caching. You just have to enable it and Astronoby will internally save some calculations that are redundant, making the total computation faster. Then, we are going to use some trigonometry to compute the daily elongation, and save the 30 largest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Assuming you have already downloaded&lt;/span&gt;
&lt;span class="c1"&gt;# and loaded the ephemeris, cf above.&lt;/span&gt;

&lt;span class="c1"&gt;# Enable caching and better performance&lt;/span&gt;
&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Iterate over every day of the year&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_object&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# Let's check elongation at midnight UTC&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute Sun and Venus positions&lt;/span&gt;
    &lt;span class="n"&gt;sun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;instant: &lt;/span&gt;&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;venus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Venus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;instant: &lt;/span&gt;&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract their astrometric unit vectors&lt;/span&gt;
    &lt;span class="c1"&gt;# We normalize the position vectors of Venus and the Sun&lt;/span&gt;
    &lt;span class="c1"&gt;# to compute the angle between them.&lt;/span&gt;
    &lt;span class="n"&gt;venus_unit_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;venus&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astrometric&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;m&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;venus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astrometric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;m&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;sun_unit_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astrometric&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;m&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astrometric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;m&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute the elongation angle and store it&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Maths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dot_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;venus_unit_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sun_unit_vector&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;top_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_angle&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [&lt;/span&gt;
&lt;span class="c1"&gt;#   #&amp;lt;Date: 2025-01-10 ((2460686j,0s,0n),+0s,2299161j)&amp;gt;,&lt;/span&gt;
&lt;span class="c1"&gt;#   ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In 2025, the greatest elongation of Venus happened on January 10 and the best days to observe Venus were around this day.&lt;/p&gt;

&lt;p&gt;Note that if you look at the whole array, you will notice the presence of some days from June, it's because Venus reached its greatest elongation twice in 2025 (East in January, West in June).&lt;/p&gt;

&lt;h3&gt;
  
  
  Superior planets
&lt;/h3&gt;

&lt;p&gt;Now, superior planets are the ones which orbit is further from the Sun than the Earth's, which include Jupiter, Saturn, Uranus and Neptune.&lt;/p&gt;

&lt;p&gt;For these planets, the best time to observe them is at opposition, when Earth is exactly between the Sun and the planet. At that point, the planet is visible all night and reaches transit near midnight, giving the best conditions for observation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj24d6e5uikqe5hiaesg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj24d6e5uikqe5hiaesg.png" alt="Opposition configuration of a superior planet" width="364" height="376"&gt;&lt;/a&gt;&lt;small&gt;&lt;small&gt;Credits: University of Nebraska-Lincoln&lt;/small&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;The geometrical and physical definition of a planet's opposition is to have the difference between the Sun and the planet's geocentric apparent ecliptic longitude to equal 180°.&lt;/p&gt;

&lt;p&gt;Good news, we also have this information available straight from Astronoby. Let's calculate the day of opposition by finding the day when this angle is the closest to 180°.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Assuming you have already downloaded&lt;/span&gt;
&lt;span class="c1"&gt;# and loaded the ephemeris, cf above.&lt;/span&gt;

&lt;span class="c1"&gt;# Enable caching and better performance&lt;/span&gt;
&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Iterate over every day of the year&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_object&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# Let's check opposition at midnight UTC&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute Sun and Saturn positions&lt;/span&gt;
    &lt;span class="n"&gt;sun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;instant: &lt;/span&gt;&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;saturn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Saturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;instant: &lt;/span&gt;&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute the opposition angle&lt;/span&gt;
    &lt;span class="c1"&gt;# This calculates the difference in ecliptic longitude,&lt;/span&gt;
    &lt;span class="c1"&gt;# opposition is when it's 180°.&lt;/span&gt;
    &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apparent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ecliptic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;saturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apparent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ecliptic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;longitude&lt;/span&gt;

    &lt;span class="c1"&gt;# Store the absolute angle reduced by 180° for ease of use&lt;/span&gt;
    &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;top_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_angle&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [&lt;/span&gt;
&lt;span class="c1"&gt;#   #&amp;lt;Date: 2025-09-21 ((2460940j,0s,0n),+0s,2299161j)&amp;gt;,&lt;/span&gt;
&lt;span class="c1"&gt;#   ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In 2025, the opposition of Saturn will happen on September 10 and the best days to observe Saturn were around this day.&lt;/p&gt;

&lt;p&gt;And you don't have to trust my math, you can also use Astronoby to get the transit time on the opposition day to ensure Saturn will reach its maximum altitude in the middle of the night:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;opposition_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Set up the observer with your GPS coordinates&lt;/span&gt;
&lt;span class="c1"&gt;# For the example: Paris, France&lt;/span&gt;
&lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;latitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;48.8575&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;longitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.3514&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RiseTransitSetCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Saturn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;observer: &lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Compute time of Saturn's transit, Paris local time&lt;/span&gt;
&lt;span class="n"&gt;transit_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opposition_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transit_time&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"+01:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2025-09-22 00:44:30 +0100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Moon observation
&lt;/h2&gt;

&lt;p&gt;Last stop of our journey: The Moon.&lt;/p&gt;

&lt;p&gt;You might think this is easy: the Moon is visible almost every day, so observing it should be straightforward. But it's not that simple.&lt;/p&gt;

&lt;p&gt;It depends on what you want to observe, when, and in what conditions.&lt;/p&gt;

&lt;p&gt;First, the Moon rotates around the Earth in around 29 days, its phase changes every day. Whether you want to observe a full Moon or a thin crescent, the desired phase isn't available every day.&lt;/p&gt;

&lt;p&gt;Also, you probably want to observe it when it's high in the sky, visible for a long time. Observing it close to the horizon might make the observation hard due to the atmospheric refraction bending the light.&lt;/p&gt;

&lt;p&gt;You might have noticed it, but sometimes the Moon can be seen very high in the sky, and sometimes very close to the horizon. This is because the Moon's orbit is inclined by 5.14° to the ecliptic, in addition to the Earth's axial tilt to orbit of 23.44°. The combination of these tilts constantly changes how the Moon appears in the sky. There are also many small and long-term effects that have an influence but let's not get too complex.&lt;/p&gt;

&lt;p&gt;The main thing to understand is that the perfect configuration for you might happen very rarely, that's why computing when it happens with Astronoby is very helpful.&lt;/p&gt;

&lt;p&gt;For the example, I'm going to be very selective to show how precise you can get with Astronoby. Let's say I want to know the days when I can observe the Moon in the evening, not too close to the Full Moon because I want to observe craters with grazing light, when it will be high enough in the sky at transit.&lt;/p&gt;

&lt;p&gt;To be more technical, this translates into the following requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Altitude at transit at least 45°&lt;/li&gt;
&lt;li&gt;Phase angle below 110° so that the Moon doesn't appear too close to the Sun&lt;/li&gt;
&lt;li&gt;Illuminated fraction below 0.85 to avoid the Full Moon and the days around it&lt;/li&gt;
&lt;li&gt;Transit happens after noon to allow for a nice observation in the evening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how the Moon would look like when meeting all the requirements. Some craters are beautiful during this phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy342n1tbsivkn64i0pr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy342n1tbsivkn64i0pr1.png" alt="Picture of the Moon meeting all the requirements" width="384" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last note before we jump into the code. I'm going to use the &lt;code&gt;tzinfo&lt;/code&gt; gem to have an accurate local noon and dates across the year.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"tzinfo"&lt;/span&gt;

&lt;span class="c1"&gt;# Assuming you have already downloaded&lt;/span&gt;
&lt;span class="c1"&gt;# and loaded the ephemeris, cf above.&lt;/span&gt;

&lt;span class="c1"&gt;# Enable caching and better performance&lt;/span&gt;
&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Set up the observer with your GPS coordinates&lt;/span&gt;
&lt;span class="c1"&gt;# For the example: Paris, France&lt;/span&gt;
&lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;latitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;48.8575&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;longitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.3514&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Use Paris' time zone for accurate time and date conversion&lt;/span&gt;
&lt;span class="n"&gt;tz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TZInfo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Europe/Paris"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Define requirements&lt;/span&gt;
&lt;span class="n"&gt;minimum_altitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;maximum_phase_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;maximum_illuminated_fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;

&lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RiseTransitSetCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Moon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;observer: &lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Compute all the transit times of year 2025&lt;/span&gt;
&lt;span class="c1"&gt;# Thanks to the caching feature, this is quite fast&lt;/span&gt;
&lt;span class="n"&gt;transit_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;events_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transit_times&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transit_times&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_object&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;transit_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transit_time&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_date&lt;/span&gt;
    &lt;span class="n"&gt;transit_instant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transit_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute the Moon's position at transit time&lt;/span&gt;
    &lt;span class="n"&gt;moon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Moon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;instant: &lt;/span&gt;&lt;span class="n"&gt;transit_instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get the Moon's topocentric altitude&lt;/span&gt;
    &lt;span class="c1"&gt;# Altitude as seen by an observer on Earth&lt;/span&gt;
    &lt;span class="n"&gt;altitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;moon&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observed_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;horizontal&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;altitude&lt;/span&gt;

    &lt;span class="c1"&gt;# Define when noon happens in Paris that day&lt;/span&gt;
    &lt;span class="n"&gt;local_noon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Decide whether or not the requirements are met&lt;/span&gt;
    &lt;span class="n"&gt;requirements_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;altitude&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;minimum_altitude&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="n"&gt;moon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;phase_angle&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maximum_phase_angle&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="n"&gt;moon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;illuminated_fraction&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maximum_illuminated_fraction&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="n"&gt;transit_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;local_noon&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;requirements_pass&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 28&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are only 28 days in 2025 where I can observe the Moon from Paris in the particular configuration I require.&lt;/p&gt;

&lt;p&gt;Of course I was very strict for the sake of the example and the Moon is nice to observe any time it is visible.&lt;/p&gt;

&lt;p&gt;In this post, we saw how Astronoby can compute the best days for observing the Sun, planets, and Moon, from solstices and elongations to oppositions and lunar phases. These tools help astronomers plan sessions months in advance. In future releases, Astronoby will support even more events. Until then, I'd love to hear what you'd like to compute with Astronoby!&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Cover picture credits: NASA&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>astronoby</category>
      <category>programming</category>
    </item>
    <item>
      <title>Where on Earth do shadows disappear? Finding the sub-solar point with Ruby</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Fri, 20 Jun 2025 14:58:49 +0000</pubDate>
      <link>https://dev.to/rhannequin/where-on-earth-do-shadows-disappear-finding-the-sub-solar-point-with-ruby-5hm</link>
      <guid>https://dev.to/rhannequin/where-on-earth-do-shadows-disappear-finding-the-sub-solar-point-with-ruby-5hm</guid>
      <description>&lt;p&gt;There is constantly a place on Earth where you would cast no shadow. Or more exactly, if you were a straight object like a traffic cone, you would not cast any visible shadow.&lt;/p&gt;

&lt;p&gt;This happens when you right at the sub-solar point, meaning "right under the Sun". At this instant, the Sun is at the &lt;em&gt;zenith&lt;/em&gt;, right above your head, 90° above the horizon.&lt;/p&gt;

&lt;p&gt;This place on Earth constantly moves, act fast if you want to snap a strangely fascinating photo. But what exactly is happening?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshvc6qdhui4eppliujof.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshvc6qdhui4eppliujof.jpg" alt="Objects looking strange as they have no visible shadow during sub-solar point event" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let's have a quick astronomical recap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Obliquity of the ecliptic
&lt;/h2&gt;

&lt;p&gt;The Earth does its revolution around the Sun on a plane called the &lt;em&gt;ecliptic&lt;/em&gt;. But as we know, the Earth also rotates on its axis in about 24 hours, along what we call the &lt;em&gt;equatorial&lt;/em&gt; plane.&lt;/p&gt;

&lt;p&gt;Our planet is tilted, meaning the ecliptic and equatorial planes are not the same. The angle between them is called the &lt;em&gt;obliquity of the ecliptic&lt;/em&gt;. Its current value is 23.44° and it slightly changes over time (millennia), but this part will be for another day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkq9ivlu6lw3b5gzghteo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkq9ivlu6lw3b5gzghteo.png" alt="Illustration of the Earth's rotation and the ecliptic" width="720" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tilt is what causes the seasons. As we go through the year, one hemisphere will have longer or shorter days.&lt;/p&gt;

&lt;p&gt;But what does this have to do with having the Sun at zenith?&lt;/p&gt;

&lt;h2&gt;
  
  
  Sub-solar point
&lt;/h2&gt;

&lt;p&gt;Because the Earth is round (if you don't agree, the rest of this article might be a bit less enjoyable) and tilted on its axis (that's the &lt;em&gt;obliquity&lt;/em&gt;), we don't all receive the light rays with the same angle.&lt;/p&gt;

&lt;p&gt;This also means there's always a place on Earth that receives the sun light perpendicularly to the surface. That's the sub-solar point!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy6zzc1ybul4wbahlpds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy6zzc1ybul4wbahlpds.png" alt="Picture of how the sunlight hits the Earth" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously this point doesn't stay there, let's not forget the Earth's rotation - it's always noon somewhere.&lt;/p&gt;

&lt;p&gt;Now let's combine the two pieces of information we have: There is a place where the sunlight hits the surface perpendicularly, and this place changes constantly because of the Earth rotation and its revolution around the Sun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the exact spot: the theory
&lt;/h2&gt;

&lt;p&gt;Now comes the fun part: finding that elusive spot.&lt;/p&gt;

&lt;p&gt;To compute it, we need 2 pieces of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Sun's apparent declination&lt;/li&gt;
&lt;li&gt;the longitude of the meridian where the real solar noon happens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lots of unfamiliar terms here, let's dig a little bit deeper and then we should be good with the theoretical stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Declination
&lt;/h3&gt;

&lt;p&gt;It's very similar as the geographic latitude, which is how North or South the location is compared to the equator. If you extended Earth's equator into space, it would trace the celestial equator. The declination is then how up or down the object is compared to the celestial equator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2jmci3uuu410yxg5fdk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2jmci3uuu410yxg5fdk.gif" alt="Visual of the definition of declination" width="530" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Solar noon
&lt;/h3&gt;

&lt;p&gt;Yes, there's a &lt;em&gt;real&lt;/em&gt; noon... and a &lt;em&gt;fake&lt;/em&gt; one. 😱&lt;/p&gt;

&lt;p&gt;Actually, it's not &lt;em&gt;fake&lt;/em&gt;, it's &lt;em&gt;mean&lt;/em&gt;. Noon is commonly known as mid-day, when the Sun is at its highest in the sky. Mean noon is a standardised time that everyone in the same time zone agrees to use. &lt;a href="https://en.m.wikipedia.org/wiki/Noon#Solar_noon" rel="noopener noreferrer"&gt;Solar noon&lt;/a&gt; is when the Sun is at it's highest in the sky for a given observer, regardless of its legal time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Actually finding the exact spot
&lt;/h3&gt;

&lt;p&gt;Finally we can move to the practical part and actually compute the sub-solar point's coordinates.&lt;/p&gt;

&lt;p&gt;We're using &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;Astronoby&lt;/a&gt;, a library that enables to compute astronomical event and data in pure Ruby. It relies on ephemerides published by official administrations and institutes like NASA/JPL or the IMCCE.&lt;/p&gt;

&lt;p&gt;The first action is to download one of these ephemerides, they are the fuel of the library. This needs to be done only once and then the file is stored locally, if you already used Astronoby it might not be necessary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ephem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"inpop19a.bsp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"tmp/inpop19a.bsp"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the ephemeris can be loaded and used by Astronoby in any computation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;ephem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ephem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tmp/inpop19a.bsp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we need to specify what time we want to know where the sub-solar point is. Astronoby abstracts time through a &lt;code&gt;Instant&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;instant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Up to this point, we have everything we need to compute the Sun's apparent coordinates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;instant: &lt;/span&gt;&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sub-solar point's latitude is right there, described by th sun's apparent declination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sub_solar_latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apparent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equatorial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;declination&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The longitude is slightly more complicated as it is the difference between the apparent right ascension and the Greenwich &lt;a href="https://en.wikipedia.org/wiki/Sidereal_time" rel="noopener noreferrer"&gt;sidereal time&lt;/a&gt;. We didn't explain these two new notions for simplicity's sake, and it might be confusing to compare and angle with a time, but astronomy allows that. Let's just say right ascension is the celestial equivalent of longitude, paired with declination it forms a full coordinate system for the sky. Sidereal time is a time scale that is based on the Earth's rate of rotation measured relative to the fixed stars.&lt;/p&gt;

&lt;p&gt;The longitude is the difference between right ascension and sidereal time, normalised in a range between -180° and 180°.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;right_ascension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apparent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equatorial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;right_ascension&lt;/span&gt;
&lt;span class="n"&gt;sidereal_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;GreenwichSiderealTime&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;
&lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;right_ascension&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_hours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sidereal_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Normalise to range [-180°, 180°]&lt;/span&gt;
&lt;span class="n"&gt;sub_solar_longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, we have our coordinates!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"At &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, the sub-solar point is at:"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sub_solar_latitude&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°,&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sub_solar_longitude&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°"&lt;/span&gt;

&lt;span class="c1"&gt;# Output:&lt;/span&gt;
&lt;span class="c1"&gt;# At 2025-06-20 00:00:00 UTC, the sub-solar point is at:&lt;/span&gt;
&lt;span class="c1"&gt;#   23.43°,-179.62°&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  But wait, there's more!
&lt;/h2&gt;

&lt;p&gt;You might be aware, but this post is supposedly published the day before June 21, 2025's June &lt;a href="https://en.wikipedia.org/wiki/Solstice" rel="noopener noreferrer"&gt;solstice&lt;/a&gt;. What's a solstice you may say? This is the moment in time where the Sun will reach it's highest or lowest (depending on the observer's hemisphere) altitude in the sky for the year.&lt;/p&gt;

&lt;p&gt;The first thing, is that you can compute this instant easily with Astronoby.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EquinoxSolstice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;june_solstice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2025-06-21 02:42:19 UTC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember when we said at the beginning of this post that the Earth's tilt was 23.44°. Well, the second thing I want to show you is that with the beauty of geometry, we can compute this angle back by looking at the sub-solar point's coordinates during solstice!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EquinoxSolstice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;june_solstice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;instant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ... same instructions as before&lt;/span&gt;

&lt;span class="c1"&gt;# Output:&lt;/span&gt;
&lt;span class="c1"&gt;# At 2025-06-21 02:42:19 UTC, the sub-solar point is at:&lt;/span&gt;
&lt;span class="c1"&gt;#   23.44°,139.86°&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes sense as solstices are moments where the Earth's tilt as the most effect on how we receive sunlight.&lt;/p&gt;

&lt;p&gt;And guess what, we've actually named the extreme sub-solar latitudes 23.44° and -23.44° and you might have already heard them: &lt;a href="https://en.wikipedia.org/wiki/Tropics" rel="noopener noreferrer"&gt;Tropic&lt;/a&gt; of Cancer and Tropic of Capricorn.&lt;/p&gt;

&lt;p&gt;Who would've guessed we could play with astronomy and geography, in Ruby? Now you can!&lt;/p&gt;

&lt;p&gt;Check out on &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;Astronoby&lt;/a&gt; for even more celestial wonders seen from Earth!&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Credits:&lt;/small&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;small&gt;&lt;a href="https://www.chegg.com/homework-help/questions-and-answers/1-look-figure-december-21-left--latitude-subsolar-point-date-1-point-q45311615" rel="noopener noreferrer"&gt;https://www.chegg.com/homework-help/questions-and-answers/1-look-figure-december-21-left--latitude-subsolar-point-date-1-point-q45311615&lt;/a&gt;&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;&lt;small&gt;&lt;a href="https://skyandtelescope.org/astronomy-resources/what-is-the-ecliptic/" rel="noopener noreferrer"&gt;https://skyandtelescope.org/astronomy-resources/what-is-the-ecliptic/&lt;/a&gt;&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;&lt;small&gt;&lt;a href="http://spiff.rit.edu/classes/phys445/lectures/radec/radec.html" rel="noopener noreferrer"&gt;http://spiff.rit.edu/classes/phys445/lectures/radec/radec.html&lt;/a&gt;&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>astronoby</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Thu, 19 Jun 2025 21:08:38 +0000</pubDate>
      <link>https://dev.to/rhannequin/-4jpj</link>
      <guid>https://dev.to/rhannequin/-4jpj</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/gkosmo/check-the-code-health-of-your-rails-app-ag5" class="crayons-story__hidden-navigation-link"&gt;Check the code health of your rails app&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/gkosmo" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F106316%2F516c6ac7-293a-44e7-8116-996c67aabd5b.jpeg" alt="gkosmo profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/gkosmo" class="crayons-story__secondary fw-medium m:hidden"&gt;
              George Kosmopoulos
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                George Kosmopoulos
                
              
              &lt;div id="story-author-preview-content-2599887" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/gkosmo" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F106316%2F516c6ac7-293a-44e7-8116-996c67aabd5b.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;George Kosmopoulos&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/gkosmo/check-the-code-health-of-your-rails-app-ag5" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 17 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/gkosmo/check-the-code-health-of-your-rails-app-ag5" id="article-link-2599887"&gt;
          Check the code health of your rails app
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rails"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rails&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ruby"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ruby&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gem"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gem&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/health"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;health&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/gkosmo/check-the-code-health-of-your-rails-app-ag5" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/gkosmo/check-the-code-health-of-your-rails-app-ag5#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>rails</category>
      <category>ruby</category>
      <category>gem</category>
      <category>health</category>
    </item>
    <item>
      <title>Astronoby v0.7.0: Planets and ephemerides</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Mon, 12 May 2025 10:19:43 +0000</pubDate>
      <link>https://dev.to/rhannequin/astronoby-v070-planets-and-ephemerides-85b</link>
      <guid>https://dev.to/rhannequin/astronoby-v070-planets-and-ephemerides-85b</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr at the end&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After months of refactoring, &lt;a href="https://github.com/rhannequin/astronoby/releases/tag/v0.7.0" rel="noopener noreferrer"&gt;Astronoby v0.7.0 is released&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This new version introduces two major changes: Planets and ephemerides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick glossary:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;em&gt;ephemeris&lt;/em&gt; is a software or file that helps generating positions and velocities of celestial bodies&lt;/li&gt;
&lt;li&gt;The plural form is &lt;em&gt;ephemerides&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Astronoby ❤️ Ephem
&lt;/h2&gt;

&lt;p&gt;5 months ago, &lt;a href="https://dev.to/rhannequin/introducing-ephem-5827"&gt;Ephem&lt;/a&gt; was released, a Ruby library to compute position and velocity vectors of the major planets of the Solar System using SPICE Kernel files (SPK).&lt;/p&gt;

&lt;p&gt;Ephem is now v0.3.0 and supports two sources of SPK:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ssd.jpl.nasa.gov/planets/eph_export.html" rel="noopener noreferrer"&gt;NASA/JPL Development Ephemeris&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imcce.fr/inpop" rel="noopener noreferrer"&gt;IMCCE INPOP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new version of Astronoby adds Ephem as a dependency to enable the extreme precision of calculating planet coordinates. The Development Ephemeris files from the JPL are, for instance, used for actual spacecraft navigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planets!
&lt;/h2&gt;

&lt;p&gt;While only the Sun and the Moon were supported as celestial bodies in previous Astronoby versions, now all the major Solar System planets can be manipulated, from Mercury to Neptune.&lt;/p&gt;

&lt;p&gt;As an observer on Earth, it is possible to compute with great precision where a planet is in the sky, like the Sun and the Moon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;All the changes are documented in the &lt;a href="https://github.com/rhannequin/astronoby/releases/tag/v0.7.0" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; and the whole library is documented in the &lt;a href="https://github.com/rhannequin/astronoby/wiki" rel="noopener noreferrer"&gt;repository's Wiki&lt;/a&gt;, but let me give you a quick look at what is now possible with Astronoby.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download and load an ephemeris
&lt;/h3&gt;

&lt;p&gt;The ephemeris is the main source of data for Astronoby, which uses it to compute different kind of positions for different needs.&lt;/p&gt;

&lt;p&gt;Ephemerides can be downloaded directly using Astronoby.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ephem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"de440s.bsp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"data/ephem.bsp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will download the file and store it at the specified location. You only need to do it once, you can then load it any time you want to compute data and events with Astronoby.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;ephem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ephem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data/ephem.bsp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deal with time with &lt;code&gt;Instant&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Because ephemerides require extra precision and manipulate a different time scale, the new version of Astronoby introduced &lt;code&gt;Astronoby::Instant&lt;/code&gt;, a value object to deal with time scales.&lt;/p&gt;

&lt;p&gt;You can still use &lt;code&gt;Time&lt;/code&gt; and &lt;code&gt;Date&lt;/code&gt;, but it is necessary to go through &lt;code&gt;Instant&lt;/code&gt; in most calculations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;instant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_time&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2025-11-05 00:00:00 UTC&lt;/span&gt;
&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_date&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Date: 2025-11-05 ((2460985j,0s,0n),+0s,2299161j)&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Instantiate a planet
&lt;/h3&gt;

&lt;p&gt;Once you have an &lt;code&gt;ephem&lt;/code&gt; and an &lt;code&gt;instant&lt;/code&gt;, you have everything you need to instantiate a Solar System body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;saturn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Saturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;instant: &lt;/span&gt;&lt;span class="n"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can compute 5 different positions: &lt;code&gt;geometric&lt;/code&gt;, &lt;code&gt;astrometric&lt;/code&gt;, &lt;code&gt;mean_of_date&lt;/code&gt;, &lt;code&gt;apparent&lt;/code&gt; and &lt;code&gt;topocentric&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Most users will be interested in &lt;code&gt;apparent&lt;/code&gt; and &lt;code&gt;topocentric&lt;/code&gt;. The apparent position is corrected and geocentric, it describes how the object would be seen from the centre of Earth, while the topocentric position is centred as an observer on Earth.&lt;/p&gt;

&lt;p&gt;A topocentric position can be computed using an &lt;code&gt;observer&lt;/code&gt; object that already existed in the previous versions of Astronoby:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;latitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;55.8652&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;longitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;4.3062&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;topocentric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saturn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observed_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From a topocentric position, you can access horizontal coordinates, which are basically where the object is up/down and left/right in the sky:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;topocentric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;horizontal&lt;/span&gt;

&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;altitude&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 20.5112&lt;/span&gt;

&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;azimuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 226.8246&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rising, transit and setting times
&lt;/h3&gt;

&lt;p&gt;This new version also introduces a new calculator for rising, transit and setting times, performant and supporting ranges of dates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RiseTransitSetCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Saturn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;observer: &lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;ephem: &lt;/span&gt;&lt;span class="n"&gt;ephem&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;events_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rising_times&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [2025-11-01 15:40:09 UTC, 2025-11-02 15:36:09 UTC]&lt;/span&gt;

&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transit_times&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [2025-11-01 21:19:55 UTC, 2025-11-02 21:15:49 UTC]&lt;/span&gt;

&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setting_times&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [2025-11-01 03:03:49 UTC, 2025-11-02 02:59:38 UTC]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is still possible to compute Moon phases, twilight times and other data, check out the &lt;a href="https://github.com/rhannequin/astronoby/wiki" rel="noopener noreferrer"&gt;Wiki&lt;/a&gt; to discover all the library's features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precision
&lt;/h2&gt;

&lt;p&gt;Not only does this new version enable the user to manipulate Solar System planets, but it also does it with extreme precision.&lt;/p&gt;

&lt;p&gt;Topocentric positions from Astronoby have an error margin of less than 10 arc seconds, which corresponds to half the size of Saturn in the sky when it is the closest to Earth.&lt;/p&gt;

&lt;p&gt;This precision is by far enough to guide automatically any amateur telescope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This new version enables Astronoby to enter a new phase: computing any phenomenon with accuracy. Thanks to the help of ephemerides, Astronoby has access to extremely precise data and can use it in algorithms and provide many more features in the future.&lt;/p&gt;

&lt;p&gt;While I was limited to the data provided by the few books the library was based on, with ephemerides Astronoby can be autonomous and write its own story.&lt;/p&gt;

&lt;p&gt;Don't hesitate to reach out to me here or on GitHub if you have any question or for support to integrate this new version, I will be more than happy to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Astronoby v0.7.0 is out&lt;/li&gt;
&lt;li&gt;It uses &lt;a href="https://dev.to/rhannequin/introducing-ephem-5827"&gt;Ephem&lt;/a&gt; for extreme accuracy&lt;/li&gt;
&lt;li&gt;All planets of the Solar System can be observed from Earth&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Credit cover picture: &lt;a href="https://gostargazing.co.uk" rel="noopener noreferrer"&gt;https://gostargazing.co.uk&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>astrometry</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing Ephem</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Mon, 13 Jan 2025 15:07:36 +0000</pubDate>
      <link>https://dev.to/rhannequin/introducing-ephem-5827</link>
      <guid>https://dev.to/rhannequin/introducing-ephem-5827</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr at the end&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I am glad to announce a new Ruby gem on astronomy has been published: &lt;em&gt;Ephem&lt;/em&gt;, a library that produces ephemerides from NASA's JPL Development Ephemeris series.&lt;/p&gt;

&lt;h2&gt;
  
  
  JPL DE4XX
&lt;/h2&gt;

&lt;p&gt;The Jet Propulsion Laboratory (JPL) produces a series of mathematical models called Development Ephemeris (DE), which are used for spacecraft navigation and astronomy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhntcw2jfh8ttdw76cw8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhntcw2jfh8ttdw76cw8.png" alt="JPL center in Pasadena, California" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Such an ephemeris is available through a SPK file, or SPICE Kernel file, a complex binary file containing mathematical parameters to compute the position of Solar System bodies based on time.&lt;/p&gt;

&lt;p&gt;The precision delivered by these models is so high that space agencies use it to send spacecraft into space. As an order of magnitude, while the planet Mars is usually located within a range of 50 to 250 million kilometres from the Earth, they are expected to predict its location with a margin error of one kilometre.&lt;/p&gt;

&lt;p&gt;These kernels are large and complex and follow an arbitrary format of headers, segments, and coefficients that is unfriendly to beginners. However, once the right coefficients are found and used in the right mathematical formula, barycentric Cartesian coordinates are produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solar System Cartesian coordinates
&lt;/h2&gt;

&lt;p&gt;While we are used to coordinates relative to one's town, country or even the Earth, a DE kernel produces a more absolute series of coordinates, relative to barycentres in the Solar System.&lt;/p&gt;

&lt;p&gt;A [barycentre] is the centre of mass of two or more bodies. In the same way, the point of balance for two different objects on a plank depends on their mass, Solar System bodies such as planets or even the Sun are related to their barycentre.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77b6jksexbvllcba93kn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77b6jksexbvllcba93kn.gif" alt="Animation of a barycentre with two objects, one with a large mass and the other with a small mass. They both orbit the barycentre with different amplitudes based on their mass." width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, Cartesian coordinates are a 3-D space coordinate system of three axes going through a single origin. Often named &lt;em&gt;x, y, z&lt;/em&gt;, these are coordinates where an object is located in space relative to the origin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flube2w12i31ij9hdzhtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flube2w12i31ij9hdzhtf.png" alt="Illustration of Cartesian coordinates showing 3 planes for each x,y,z axis, joined at a centre" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is an example in more practical words: JPL DE405 enables us to compute the Earth-Moon system's (barycentre) location from the Solar System's barycentre. It can also calculate the Earth's location from the Earth-Moon barycentre. Therefore, through a simple formula, it is possible to compute the Earth's location from the Solar System's barycentre.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use these coordinates
&lt;/h2&gt;

&lt;p&gt;Now the question is: What can we do with such raw data?&lt;/p&gt;

&lt;p&gt;As we can see these coordinates as absolute, we can then convert them into coordinates that are useful to an observer.&lt;/p&gt;

&lt;p&gt;Through a list of trigonometry functions, time and referential conversions and many corrections due to how complex the motion of celestial bodies is, the barycentric coordinates can be converted into topocentric coordinates, also known as geodetic coordinates or even horizontal coordinates. From Ephem's data, it is possible to calculate the altitude and azimuth of a celestial object in the sky for a given time and place on Earth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The coordinates produced by Ephem can be used to observe the sky with extreme precision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While Ephem remains its own gem so that the community can access raw data, it will become a dependency of &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;Astronoby&lt;/a&gt; to exploit its full potential. Having access to such precise and accurate data will open the door to all possible computations of celestial events with the greatest accuracy available to the public. This will enable Astronoby to truly become a scientific library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Here is a common use case of the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Download and store the SPICE binary kernel file&lt;/span&gt;
&lt;span class="no"&gt;Ephem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"de421.bsp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"tmp/de421.bsp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load the kernel&lt;/span&gt;
&lt;span class="n"&gt;spk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ephem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SPK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tmp/de421.bsp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Define the center and target bodies&lt;/span&gt;
&lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# Solar system barycenter&lt;/span&gt;
&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="c1"&gt;# Jupiter&lt;/span&gt;

&lt;span class="c1"&gt;# Get the right segment&lt;/span&gt;
&lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Get the position and velocity of the target body at a given time&lt;/span&gt;
&lt;span class="c1"&gt;# The time is expressed in Julian Date TDB&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2460676.5&lt;/span&gt; &lt;span class="c1"&gt;# 2025-01-01 00:00 UTC&lt;/span&gt;
&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compute_and_differentiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Display the position and velocity vectors&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Position: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Position: Vector[157123190.6507038, 684298787.8592143, 289489366.7262833]&lt;/span&gt;
&lt;span class="c1"&gt;# The position is expressed in km&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Velocity: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;velocity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Velocity: Vector[-1117315.1437825128, 254177.26336095092, 136149.03901534996]&lt;/span&gt;
&lt;span class="c1"&gt;# The velocity is expressed in km/day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What if I want to use it right now?
&lt;/h2&gt;

&lt;p&gt;If you want to use this data immediately and not wait for Astronoby to use it, or if you don't want to rely on Astronoby at all, I got you covered. You will find &lt;a href="https://gist.github.com/rhannequin/92651ea26a387d4b5d90952942808e77" rel="noopener noreferrer"&gt;here&lt;/a&gt; a simple Ruby script that computes the position of Saturn on a specified date and time and displays the horizontal coordinates (altitude and azimuth) for an observer on Earth.&lt;/p&gt;

&lt;p&gt;Please be aware the results will be approximate. Many tiny corrections should be added to the calculations to compute accurate coordinates. However, for amateur purposes, you will probably find the results accurate enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Access to astronomical data with this level of precision is a first time in the Ruby ecosystem. While Ephem is only v0.1, the main usage is covered and accurate data can already be used by the community.&lt;/p&gt;

&lt;p&gt;It will help Astronoby reach its full potential and provide more and more features while ensuring accuracy and precision.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/rhannequin/ruby-ephem" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for more usage information and please spread the word so that we can improve the library from feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/rhannequin/ruby-ephem" rel="noopener noreferrer"&gt;Ephem&lt;/a&gt; v0.1 is out: &lt;a href="https://rubygems.org/gems/ephem" rel="noopener noreferrer"&gt;https://rubygems.org/gems/ephem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Supports computing Solar System's planets Cartesian coordinates&lt;/li&gt;
&lt;li&gt;Based on NASA JPL Development Ephemeris files&lt;/li&gt;
&lt;li&gt;Accuracy from our point of view is estimated to be below 0.1 arcseconds&lt;/li&gt;
&lt;li&gt;Will be used in &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;Astronoby&lt;/a&gt; to compute geocentric coordinates&lt;/li&gt;
&lt;li&gt;Such accuracy allows for computing any astronomical event and data with confidence&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;Credit cover picture: &lt;a href="https://www.highpointscientific.com/astronomy-hub/post/astronomy-101/8-things-you-need-to-know-about-the-8-planets-in-our-solar-system" rel="noopener noreferrer"&gt;https://www.highpointscientific.com/astronomy-hub/post/astronomy-101/8-things-you-need-to-know-about-the-8-planets-in-our-solar-system&lt;/a&gt;&lt;/small&gt;&lt;br&gt;
&lt;small&gt;Credit JPL center: &lt;a href="https://www.jpl.nasa.gov/annual-report/today-tomorrow/" rel="noopener noreferrer"&gt;https://www.jpl.nasa.gov/annual-report/today-tomorrow/&lt;/a&gt;&lt;/small&gt;&lt;br&gt;
&lt;small&gt;Credit barycenter animation: &lt;a href="https://en.wikipedia.org/wiki/Barycenter_(astronomy)" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Barycenter_(astronomy)&lt;/a&gt;&lt;/small&gt;&lt;br&gt;
&lt;small&gt;Credit Cartesian coordinates: &lt;a href="https://fr.m.wikiversity.org/wiki/Fichier:3D_coordinate_system.svg" rel="noopener noreferrer"&gt;https://fr.m.wikiversity.org/wiki/Fichier:3D_coordinate_system.svg&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>astrometry</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Vision for Astronoby - Call for contributors and maintainers</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Wed, 18 Dec 2024 10:13:16 +0000</pubDate>
      <link>https://dev.to/rhannequin/vision-for-astronoby-call-for-contributors-and-maintainers-2n6p</link>
      <guid>https://dev.to/rhannequin/vision-for-astronoby-call-for-contributors-and-maintainers-2n6p</guid>
      <description>&lt;h2&gt;
  
  
  Genesis
&lt;/h2&gt;

&lt;p&gt;I started building &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;Astronoby&lt;/a&gt; in April 2022. My initial ambition was to provide astronomical data in pure Ruby without the need for another programming language or for integrating a third-party API. Astronomical data are by definition universal, they should be accessible and understandable by anyone. They don't belong to a copy-righted website and they should not be accessible only to a niche of programmers.&lt;/p&gt;

&lt;p&gt;My goal was to build a Ruby library that could provide data and be self-documented so that the calculations, geometrical and physical notions could be understood by just reading the code.&lt;/p&gt;

&lt;p&gt;I was also aiming for decent accuracy, knowing I could never compare with official agencies and institutes or libraries with thousands of contributors for several decades, but good enough to cover most non-professional needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;p&gt;One week ago, &lt;a href="https://github.com/rhannequin/astronoby/releases/tag/v0.6.0" rel="noopener noreferrer"&gt;version 0.6.0 was released&lt;/a&gt; and with it several bug fixes, but also a benchmark to start having a proper look at the accuracy of the library.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/rhannequin/astronoby/tree/main/benchmark" rel="noopener noreferrer"&gt;current benchmark&lt;/a&gt; could greatly benefit from a more scientific approach and compare more data, but it turns out Astronoby is currently decently accurate. It is a few minutes away from the Sun and the Moon setting and rising times in most cases. The Sun and Moon's coordinates are usually less than half an arc-minute of margin error which represents approximately an angular distance 60 times smaller than the full Moon in the sky (this is not documented at the moment).&lt;/p&gt;

&lt;p&gt;However, I have to admit not everything is perfect.&lt;/p&gt;

&lt;p&gt;On the self-documented aspect, I have to be honest and say the current implementation does not enable anyone to understand celestial mechanics by just reading the code. There are also some notions I still don't understand myself that I managed to implement anyway.&lt;/p&gt;

&lt;p&gt;This certainly didn't help encourage external help from the community. If it requires having access to the same books as me, reading the algorithms and trying to understand how I implemented them to contribute to Astronoby, I know why no one so far submitted a pull request on GitHub for a fix or a new feature. Contributing currently requires more than just Ruby knowledge; it is not newcomers-friendly.&lt;/p&gt;

&lt;p&gt;On the accuracy aspect, the library is going to face some limits sooner or later. Because the algorithms are based on books with simplified calculations and definitions, there will always be some imprecision compared to what the current state of knowledge allows us. Even the different books that serve as sources don't always share the same level of precision, or the same methods, which doesn't help find the best and yet still most accurate implementation.&lt;/p&gt;

&lt;p&gt;Finally, I regularly discover new sources of knowledge and I understand there are tools and models available for programmers to reach high accuracy but to the cost of having formulas with dozens of terms and absolutely no documentation -readable by a non-PhD person- to help understand celestial mechanics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vision for the future of Astronoby
&lt;/h2&gt;

&lt;p&gt;I don't want to lose the battle on both sides. I want Astronoby to be the most accurate and performant astronomy library for Ruby. Still, I also want Astronoby to be a source of knowledge and inspiration for anyone who would like to learn more about how it works or even how to implement it themselves. The Ruby community deserves a Ruby gem that fulfils both of these goals.&lt;/p&gt;

&lt;p&gt;I have the feeling Astronoby should be a library made of two distinct yet similar projects.&lt;/p&gt;

&lt;p&gt;One could be a project for accuracy. By integrating physical models and with the inspiration of existing important projects like &lt;a href="https://rhodesmill.org/skyfield/" rel="noopener noreferrer"&gt;Skyfield&lt;/a&gt; or &lt;a href="https://www.astropy.org/" rel="noopener noreferrer"&gt;Astropy&lt;/a&gt;, this project could focus on providing the most accurate and performant results possible in Ruby. Contributors could help optimise the code, running benchmarks, and covering as many use cases as possible.&lt;/p&gt;

&lt;p&gt;Another project could be education. With a focus on the quality of implementation, maintainability, and documentation of both the code and the astronomical concepts, this project would provide "good enough" results with code and documentation that would enable any contributor to help make astronomy more accessible to the community.&lt;/p&gt;

&lt;p&gt;To give a practical example, let's take the location of a planet in the sky. To have a decent estimation of coordinates, it is necessary to calculate the mean and true anomaly, to include perturbations of the orbit or perturbations from the observer, and also to define all these concepts so that it's easier to understand what's going on and apply a detailed algorithm. To have a high-quality prediction, there are some models like &lt;a href="https://en.wikipedia.org/wiki/VSOP_model" rel="noopener noreferrer"&gt;VSOP&lt;/a&gt; or short-period ephemeris like &lt;a href="https://ssd.jpl.nasa.gov/planets/eph_export.html" rel="noopener noreferrer"&gt;DE 421&lt;/a&gt; to apply formulas and generate extremely high-accuracy results. Both aspects seem relevant and useful to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call for contributors
&lt;/h2&gt;

&lt;p&gt;For too long I have kept the project unwelcoming. It is already difficult to dig into an unknown library and try to help, if it also requires understanding algorithms that I don't always understand myself, there is no way anyone would like to join.&lt;/p&gt;

&lt;p&gt;With two projects and two focuses, I want to make Astronoby welcoming to newcomers, either interested in having a high-accuracy astronomy library in Ruby or having an educational astronomy Ruby gem for the community.&lt;/p&gt;

&lt;p&gt;To the courageous folks who read until this point, join me and help me build Astronoby for the community. Join a group effort to make Astronoby not only the best astronomy Ruby library but also an efficient, accurate and educational astronomy library for the whole programming community. You can be either a curious developer, a scientist looking for a way to share your knowledge or someone who wants to help the community in any way.&lt;/p&gt;

&lt;p&gt;I'm happy to share more ideas I have in mind and to discover ideas from others in the comments section or on GitHub issues. We could also consider having periodic pair-programming sessions work directly on the codebase or Q&amp;amp;A events to learn more about the project and its current state.&lt;/p&gt;

&lt;p&gt;Thank you.&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Cover picture credit: Pixabay&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
    </item>
    <item>
      <title>I counted the number of visible sunsets with Ruby</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Tue, 02 Apr 2024 12:30:24 +0000</pubDate>
      <link>https://dev.to/rhannequin/i-counted-the-number-of-visible-sunsets-with-ruby-1ap7</link>
      <guid>https://dev.to/rhannequin/i-counted-the-number-of-visible-sunsets-with-ruby-1ap7</guid>
      <description>&lt;p&gt;I live in a flat in the city. There are buildings around me, so I'm not exposed to direct Sunlight all day long.&lt;/p&gt;

&lt;p&gt;My living room is West-facing, but when I moved there I quickly discovered that I wasn't able to enjoy and beautiful sunset every day (if you exclude the fact that the weather in Paris is 💩). This could look counter-intuitive. We learn in school that the Sun rises in the East and sets in the West. So first, let's have a quick recap about the apparent motion of the Sun in the sky.&lt;/p&gt;

&lt;h2&gt;
  
  
  Axis tilt and seasons
&lt;/h2&gt;

&lt;p&gt;&lt;small&gt;Credit: &lt;a href="https://www.timeanddate.com/astronomy/axial-tilt-obliquity.html" rel="noopener noreferrer"&gt;timeanddate.com&lt;/a&gt;&lt;/small&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2akkezwsgyk2o8tkpliy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2akkezwsgyk2o8tkpliy.png" alt="Earth obliquity represented on the globe with the equator and the ecliptic plane" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Earth orbits around the Sun. But the Earth's rotation axis is not perpendicular to the orbits' plane, we call this angle the &lt;strong&gt;axial tilt&lt;/strong&gt;, or &lt;strong&gt;obliquity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The axial tilt is approximately 23.44°. It slightly changes throughout millennials, but this has no impact on the rest of this article.&lt;/p&gt;

&lt;p&gt;This angle is responsible for the seasons we encounter on Earth, especially in temperate regions. One hemisphere is always more exposed to the rays of the Sun than the other one, except for the two equinoxes.&lt;/p&gt;

&lt;p&gt;The other consequence is the apparent motion of the Sun in the sky. The duration of sunlight exposure is higher in the summer than in the winter, but the Earth always rotates at the same speed: this means &lt;strong&gt;the Sun appears higher in the sky during the summer than in the winter&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Evolution of the Sun's azimuth
&lt;/h2&gt;

&lt;p&gt;&lt;small&gt;Credit: &lt;a href="https://www.rmg.co.uk/stories/topics/summer-winter-solstices-explained-how-sun-determines-longest-shortest-days-year" rel="noopener noreferrer"&gt;Between Solstices by György Soponyaim&lt;/a&gt;&lt;/small&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9p2a4m9qb6n4zfhxgeq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9p2a4m9qb6n4zfhxgeq3.png" alt="Different locations of the Sun in the sky during both solstices and both equinoxes" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Sun rises almost exactly in the East and almost exactly in the West only during the days of equinoxes. The rest of the year, the &lt;strong&gt;azimuth&lt;/strong&gt; of these phenomena changes over time.&lt;/p&gt;

&lt;p&gt;The azimuth is the angle with the North direction on the horizontal plane, or more simply the horizon for an observer. So, the Sun's azimuth is its &lt;strong&gt;location on the horizontal axis&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;During the summer, the Sun rises and sets more in the North direction. For example, from Paris, during the June solstice, the Sun rises at 51° (or North-East) and sets at 308° (or North-West). During the December solstice, it rises at 126° (or South-East) and sets at 233° (or South-West).&lt;/p&gt;

&lt;p&gt;This difference in time is highly influenced by the observer's latitude. At the equator, the azimuth difference between the two solstices is less than 50°, while it's more than 100° in northern locations like Oslo.&lt;/p&gt;
&lt;h2&gt;
  
  
  Observing the sunset from my living room
&lt;/h2&gt;

&lt;p&gt;After all this explanation, let's get back to business.&lt;/p&gt;

&lt;p&gt;From my living room, the South direction is obstructed by a large building. For a significant part of the year, the Sun sets behind it. To be more precise, as long as the sunset azimuth is too much in the South, the Sun is hidden.&lt;/p&gt;

&lt;p&gt;As you might know, I am building a Ruby gem called &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;&lt;code&gt;astronoby&lt;/code&gt;&lt;/a&gt; to compute astronomical phenomena. I recently released a new version that includes information about the Sun, and the sunset azimuth is part of it.&lt;/p&gt;

&lt;p&gt;I approximately estimated the border of the building to be 273° on the horizon, when I sit on my couch. In theory, as long as the sunset azimuth is greater than 273°, it should not be hidden by the building.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;astronoby&lt;/code&gt;, I can compute the sunset azimuth of each day of the year and select only those within the limit of visibility for me.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"astronoby"&lt;/span&gt;

&lt;span class="c1"&gt;# Actual coordinates hidden for privacy reasons&lt;/span&gt;
&lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;latitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;longitude: &lt;/span&gt;&lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;elevation: &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;azimuth_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;273&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;visible_dates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;noon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Epoch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;noon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;epoch: &lt;/span&gt;&lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;setting_azimuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setting_azimuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;observer: &lt;/span&gt;&lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;setting_azimuth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;azimuth_limit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "2024-03-26"&lt;/span&gt;

&lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "2024-09-16"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final results
&lt;/h2&gt;

&lt;p&gt;In theory, if the weather is fine, I should be able to enjoy a nice sunset from my living room couch from the 26th of March to the 16th of September.&lt;/p&gt;

&lt;p&gt;The Ruby part of this article might feel a bit disappointing, we spent two sections talking about astronomy while the dates can be computed in barely 10 lines of code. This is however an opportunity for me to brag and share that the &lt;code&gt;astronoby&lt;/code&gt; library is powerful.&lt;/p&gt;

&lt;p&gt;Knowing the location of the Sun in the sky at any time as an observer anywhere in the world has many applications. From the Sun's exposure to the quantity of energy received from the sky, the &lt;code&gt;astronoby&lt;/code&gt; gem opens new doors to Ruby developers.&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Cover picture credits: &lt;a href="https://www.vecteezy.com/vector-art/2042714-dramatic-city-sunset-sky" rel="noopener noreferrer"&gt;&lt;em&gt;Dramatic City Sunset Sky Pro Vector by John Alberton&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>webdev</category>
      <category>astronomy</category>
    </item>
    <item>
      <title>astronoby gem v0.1.0 released</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Wed, 28 Feb 2024 10:30:40 +0000</pubDate>
      <link>https://dev.to/rhannequin/astronoby-gem-v010-released-4big</link>
      <guid>https://dev.to/rhannequin/astronoby-gem-v010-released-4big</guid>
      <description>&lt;p&gt;&lt;strong&gt;I am proud to announce the first official non-major version of &lt;a href="https://rubygems.org/gems/astronoby" rel="noopener noreferrer"&gt;&lt;code&gt;astronoby&lt;/code&gt;&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Astronoby?
&lt;/h2&gt;

&lt;p&gt;Astronoby is a Ruby gem I created almost two years ago. It is a project meant to enable Ruby developers to compute astronomical data and events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Astronomy is universal. Astronomical knowledge doesn't belong to anyone. But it is also hard to grasp, calculate and comprehend.&lt;/p&gt;

&lt;p&gt;Almost 10 years ago, I tried to make web apps with astronomical data by scrapping websites. It felt wrong. Why should we rely on proprietary content, often protected with techniques against scrapping, to access such universal data?&lt;/p&gt;

&lt;p&gt;I then decided to go deeper and compute the data myself, with existing Python libraries such as &lt;a href="https://rhodesmill.org/pyephem/" rel="noopener noreferrer"&gt;PyEphem&lt;/a&gt; and &lt;a href="https://rhodesmill.org/skyfield/" rel="noopener noreferrer"&gt;Skyfield&lt;/a&gt;. I have nothing against Python, and to be honest the Python ecosystem is incredibly more advanced on this kind of scientific topic than Ruby will probably ever be. But I wasn't satisfied not to be able to the the programming languages &lt;em&gt;I wanted to&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This couldn't be the end of it. Is astronomical computing in Ruby really something impossible? When getting started with astronomy, astrometry and programming, do we always have to use tools so powerful and complicated that even professional astronomers use them?&lt;/p&gt;

&lt;p&gt;I created Astronoby for these reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the formulas and calculations accessible to developers of any level&lt;/li&gt;
&lt;li&gt;Bring a new tool to the list of scientific projects to the Ruby community&lt;/li&gt;
&lt;li&gt;Have fun and understand how these giant things up there move&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What now?
&lt;/h2&gt;

&lt;p&gt;This first official version is only the first step. For the first time, this gem enables developers to compute one data: the Sun's location in the sky based on an observer's location and a time. This only &lt;em&gt;real&lt;/em&gt; feature is the reason this first release is not a major one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Epoch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;48.8566&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.3522&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;sun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Astronoby&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;epoch: &lt;/span&gt;&lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;horizontal_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;horizontal_coordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;latitude: &lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;longitude: &lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;horizontal_coordinates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;altitude&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:dms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not ground breaking, but it is a first step. Later will come more features associated with the Sun, like sunrise and sunset, equinoxes and solstices. Then, some data related with the Moon will come up like phases and eclipses. After that, the planets will be introduced with multiple events to predict.&lt;/p&gt;

&lt;p&gt;The list is long and the remaining work is tremendous. This will take time, especially as one of the main goal is not only to make it work -libraries in other languages already do this- but to make it understandable, maintainable and pleasant to use for anyone enjoying astronomy or Ruby, or both.&lt;/p&gt;

&lt;p&gt;It already took me two years of inconsistent spikes to reach this first step. But I believe the new ones will be easier to get to now that some foundations are built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;I am aware this kind of project is not easy to contribute to, as most of the new features will be a Ruby translation from books I read by authors like Jean Meeus, J. L. Lawrence, Peter Duffet-Smith and Jonathan Zwart.&lt;/p&gt;

&lt;p&gt;The Ruby code though, all the good practices, performance improvements, bux fixes, documentation, are accessible to anyone. This is my first gem, I already made many mistakes I tried to slowly fix and I am aware the gem could already benefit from many improvements unrelated to adding features.&lt;/p&gt;

&lt;p&gt;I am also interested in learning more from the community what this library could be. It doesn't have to stop to features covered by a few books. A lot of things can happen, and this is the reason I am releasing this first official version: to make the project finally open to the community.&lt;/p&gt;

&lt;p&gt;Please spread the word, share your thoughts, join the project and let's enjoy our excitement for astronomy and Ruby.&lt;/p&gt;

&lt;p&gt;Cheers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rubygems.org/gems/astronoby" rel="noopener noreferrer"&gt;&lt;code&gt;astronoby&lt;/code&gt; on RubyGems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;&lt;code&gt;astronoby&lt;/code&gt; on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;Cover picture credits: &lt;a href="https://www.rmg.co.uk/stories/topics/summer-winter-solstices-explained-how-sun-determines-longest-shortest-days-year" rel="noopener noreferrer"&gt;&lt;em&gt;Between Solstices by György Soponyai&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>computing</category>
    </item>
    <item>
      <title>Astronomical computing – Ep 6 - Solving Kepler's equation in Ruby</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Tue, 09 May 2023 09:46:44 +0000</pubDate>
      <link>https://dev.to/rhannequin/astronomical-computing-ep-6-solving-keplers-equation-in-ruby-4j8h</link>
      <guid>https://dev.to/rhannequin/astronomical-computing-ep-6-solving-keplers-equation-in-ruby-4j8h</guid>
      <description>&lt;p&gt;One of the main goals of this series is to understand the math behind astronomical events calculations, and translate them into simple and maintainable code.&lt;/p&gt;

&lt;p&gt;In order to forecast where a planet will be visible in the night sky at a given moment we need to rely on topics that we haven’t yet covered such as orbital elements.&lt;/p&gt;

&lt;p&gt;Unlike far away stars which can be considered motionless&lt;sup&gt;[1]&lt;/sup&gt;, planets change position on the celestial sphere from one night to the other. Even the Sun, from our point of view, seems to be slowly moving alongside the zodiac belt throughout the year. Our first step towards finding out where a planet will be visible in the sky is to &lt;strong&gt;calculate its equatorial coordinates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Kepler%27s_laws_of_planetary_motion#Second_law" rel="noopener noreferrer"&gt;Kepler's second law&lt;/a&gt; states that &lt;strong&gt;the orbital speed of an object varies throughout its orbit&lt;/strong&gt;. With an elliptic orbit and a changing speed, calculating the planet's location on its orbit for a known date is not a linear process.&lt;/p&gt;

&lt;p&gt;We won't cover the calculations needed to compute the coordinates of a planet in this article. We will make sure to cover those in a later installment though. Still, this is an essential step to understand a planet’s motion on its orbit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some definitions
&lt;/h2&gt;

&lt;p&gt;Almost all of the following content can be traced to one single source: &lt;a href="https://mitpress.mit.edu/9780262536639/celestial-calculations/" rel="noopener noreferrer"&gt;&lt;em&gt;Celestial Calculations, A Gentle Introduction to Computational Astronomy&lt;/em&gt;&lt;/a&gt;, by J. L. Lawrence, editions MIT Press.&lt;/p&gt;

&lt;p&gt;This is not the first time I mention this book as it is one of my main sources for this series, and I cannot be more grateful of the incredible work of communication and explanation the author has produced to make all these phenomenons accessible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3knr5eg3aolg05qi60d9.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3knr5eg3aolg05qi60d9.PNG" alt="Section of ellipse showing eccentric and true anomaly" width="688" height="608"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Section of ellipse showing eccentric and true anomaly&lt;/em&gt;&lt;sup&gt;[2]&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The illustration above will be useful to understand the different definitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The red ellipse is the true anomaly&lt;/li&gt;
&lt;li&gt;The blue circle is the mean anomaly&lt;/li&gt;
&lt;li&gt;F is the main focus&lt;/li&gt;
&lt;li&gt;C is the geometrical center of both the red and blue circles&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;f&lt;/em&gt; is the true anomaly&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;E&lt;/em&gt; is the eccentric anomaly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  True anomaly
&lt;/h3&gt;

&lt;p&gt;We can consider it as being the &lt;strong&gt;angle between the body's current position and its &lt;em&gt;periapsis&lt;/em&gt;&lt;/strong&gt;, as seen from the &lt;em&gt;main focus&lt;/em&gt; of the ellipse. I can imagine how this definition is not helping, so let's go a bit further.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;main focus&lt;/em&gt; of the ellipse is basically the point around which the body rotates. When talking about planets of the Solar System, this point can be considered as the Sun&lt;sup&gt;[3]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;periapsis&lt;/em&gt; is the nearest point on the orbit to the main focus. For planets, it's called perihelion and is the point where it's the closest to the Sun.&lt;/p&gt;

&lt;p&gt;In other words, the true anomaly is the angular distance between the current position and a fixed point, as seen from the Sun.&lt;/p&gt;
&lt;h3&gt;
  
  
  Mean anomaly
&lt;/h3&gt;

&lt;p&gt;As a planet's orbit is an ellipse and its speed changes constantly depending on how close it is from the Sun, it's pretty difficult to calculate the true anomaly.&lt;/p&gt;

&lt;p&gt;To tackle this non trivial task, scholars imagined a fictive orbit, circular, on which the body would move with a constant speed, called the mean orbit. The real orbit would share its geometrical center and its periapsis with the mean orbit's center, which means the body would have the same orbital period on both orbits.&lt;/p&gt;

&lt;p&gt;The mean orbit is the &lt;strong&gt;angle&lt;/strong&gt;, from the fictive &lt;strong&gt;circle's center&lt;/strong&gt;, between the body's &lt;strong&gt;current position&lt;/strong&gt; and the &lt;strong&gt;periapsis&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The mean orbit is considerably easier to use. A body's mean position on its mean orbit with an orbital period of &lt;code&gt;n&lt;/code&gt; sidereal days, after &lt;code&gt;t&lt;/code&gt; days from a starting point (the periapsis), can be described this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;mean_anomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Eccentric anomaly
&lt;/h3&gt;

&lt;p&gt;The eccentric anomaly is another angular parameter that helps defining the position of a body on its orbit.&lt;/p&gt;

&lt;p&gt;It is the &lt;strong&gt;projection&lt;/strong&gt; of the body's current position on the &lt;strong&gt;mean orbit&lt;/strong&gt;, parallel to the line that goes through the &lt;strong&gt;mean orbit's center and the periapsis&lt;/strong&gt;.&lt;br&gt;
Technically we don't use the mean orbit but a circle called the auxiliary circle, but they're the same circle.&lt;/p&gt;

&lt;p&gt;Knowing the orbital eccentricity, and the eccentric anomaly, the true anomaly can be defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;true_anomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arc_tangent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;square_root&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tangent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, yes, this is getting weirder and weirder but please bear with me, we're getting closer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kepler's equation
&lt;/h3&gt;

&lt;p&gt;Kepler discovered that the eccentric and the mean anomalies are related by the following equation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;mean_anomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
  &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where the mean and eccentric anomalies are expressed in radians.&lt;/p&gt;

&lt;p&gt;Knowing this relation, all we have to do is to use the orbit’s orbital eccentricity along with the mean and eccentric anomalies to finally calculate the true anomaly. The calculation for the orbital eccentricity of an orbit is not covered in this article, so we can consider that we already know its value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterations
&lt;/h2&gt;

&lt;p&gt;Unfortunately, Kepler's equation is a transcendental equation, there is no known closed-form solution for it. To compute the eccentric anomaly, we must resolve the equation iteratively and get the most accurate approximation as possible.&lt;/p&gt;

&lt;p&gt;If two consecutive iterations have a difference inferior to a pre-defined accuracy parameter, then we can use the last iteration' solution as the most accurate approximation.&lt;/p&gt;

&lt;p&gt;Instead of resolving the equation defined above, we will resolve another one, defined as the &lt;strong&gt;Newton/Raphson method&lt;/strong&gt;, which is known to be more performant and more accurate.&lt;/p&gt;

&lt;p&gt;Iteratively, we will resolve the following equation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
      &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
      &lt;span class="n"&gt;mean_anomaly&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eccentric_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;i&lt;/code&gt; represents the following the current iteration.&lt;/p&gt;

&lt;p&gt;For the first iteration, where &lt;code&gt;i&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, we will have the following fixed solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mean_anomaly&lt;/code&gt; if the orbital eccentricity is 0.75 or less,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;π&lt;/code&gt; if the orbital eccentricity is more than 0.75.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, we will also introduce a maximum number of iterations, as some configuration may lead to values that never converge, and to prevent from a too small accuracy parameter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation in Ruby
&lt;/h2&gt;

&lt;p&gt;Finally the part that actually matches the article's title.&lt;/p&gt;

&lt;p&gt;The implementation is actually quite simple and uses recursion. This is one solution, other interesting implementations might be available out there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;kepler_equation_newton_raphson_iteration_scheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;mean_anomaly_in_radians&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;maximum_iteration_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;current_iteration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;solution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_iteration&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;
      &lt;span class="n"&gt;mean_anomaly_in_radians&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PI&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
        &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
        &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
        &lt;span class="n"&gt;mean_anomaly_in_radians&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
        &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
        &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solution&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"E&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_iteration&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ≈ &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;solution&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, "&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"Δ = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"E&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_iteration&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ≈ &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;solution&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_iteration&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maximum_iteration_count&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;solution_on_previous_interation&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;solution&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;kepler_equation_newton_raphson_iteration_scheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mean_anomaly_in_radians&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;orbital_eccentricity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maximum_iteration_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;current_iteration&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;solution&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy here, we compute the current iteration' solution from the previous iteration' solution, unless it is the first iteration. Recursion helps computing each iteration and passing the previous iteration' solution.&lt;br&gt;
As long as we have two solutions to compare, we check if their difference is significant, and if we have some iterations left before having to stop manually.&lt;br&gt;
I also added some logs to have a better view of what's happening.&lt;/p&gt;

&lt;p&gt;To illustrate this implementation, let's solve Kepler's equation with the Newton/Raphson method for an orbital eccentricity of 0.5 and a mean anomaly of 24.742896°.&lt;/p&gt;

&lt;p&gt;First we have to convert the mean anomaly from degrees to radians:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;radians = degrees * π / 180
        = 0.431845
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we set the accuracy parameter to 0.000002 radians, which is around 0.4 arc seconds.&lt;br&gt;
We set the maximum number of iterations to 10.&lt;/p&gt;

&lt;p&gt;Finally, we convert the solution from radians to degrees.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;kepler_equation_newton_raphson_iteration_scheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="mf"&gt;0.431845&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mf"&gt;2e-06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;180.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PI&lt;/span&gt;

&lt;span class="c1"&gt;# E0 ≈ 0.431845&lt;/span&gt;
&lt;span class="c1"&gt;# E1 ≈ 0.8151984038210852, Δ = 0.383354&lt;/span&gt;
&lt;span class="c1"&gt;# E2 ≈ 0.7856420972684516, Δ = 0.029556&lt;/span&gt;
&lt;span class="c1"&gt;# E3 ≈ 0.7853985310762203, Δ = 0.000243&lt;/span&gt;
&lt;span class="c1"&gt;# E4 ≈ 0.7853985148507631, Δ = 0.0&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; 45.00002013679163&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected answer is 45°, which differs of only 0.073" from the solution we computed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The true anomaly is one step towards our ability to compute where a planet is supposed to appear on the celestial sphere for a given time from a given place on Earth. We are one step closer to this goal and we already have the Ruby implementation ready.&lt;/p&gt;

&lt;p&gt;This also helped us understand why we need this value and in what way it is not straightforward to compute it.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[1]&lt;/sup&gt;&lt;/a&gt; This is absolutely false as everything in space moves, all the time, depending on the referential. Far away stars also rotate around the galaxy's center. The only difference with planets is that their motion appear extremely slow from our point of view, so slow that we often looks like they are fixed compared with short periods of time such as a Human life.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[2]&lt;/sup&gt;&lt;/a&gt; Source: Wikimedia Commons. Author: Brews ohare.&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[3]&lt;/sup&gt;&lt;/a&gt; In reality the planets orbit around the Solar System barycenter, which moves through time due to the different positions of planets. It is always located between the Sun's center and its corona, so we usually consider the Sun itself as the barycenter.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>computing</category>
    </item>
    <item>
      <title>Astronomical computing – Ep 5 - Locate an object in the sky</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Tue, 02 Aug 2022 11:40:00 +0000</pubDate>
      <link>https://dev.to/rhannequin/astronomical-computing-ep-5-locate-an-object-in-the-sky-i5e</link>
      <guid>https://dev.to/rhannequin/astronomical-computing-ep-5-locate-an-object-in-the-sky-i5e</guid>
      <description>&lt;p&gt;I am still reading multiple books at the same time, as they all provide different knowledge, methods and levels of detail.&lt;br&gt;
I recently finished Chapter 5 from Celestial Calculations, "Stars in the Nighttime sky".&lt;/p&gt;

&lt;p&gt;This chapter describes a very interesting method to locate an object from its equatorial coordinates, to its horizontal coordinates from an observer's point of view, and I would like to detail it here and explain all the steps. This methods is particularly interesting as it mixes many important notions, and it is one primary step to compute, in the future, planets locations in the night sky.&lt;br&gt;
Objects from the solar system move, their position in space varies from one day to another from our point of view and in real life. But, for a fixed time, they have fixed equatorial coordinates, which means we are going to be able to locate them in the sky. This is what we are going to do.&lt;/p&gt;

&lt;p&gt;Now let's forget about solar system objects and use ones that have fixed equatorial coordinates at (almost) all time: distant stars. Actually, all stars except the Sun, as they are all very distant from us, so distant that their motion cannot be perceived to us in such a short period of time as a human lifetime.&lt;/p&gt;

&lt;p&gt;With this very long introduction, here are the steps to convert equatorial to horizontal coordinates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert local civil time (LCT) to universal time (UT)&lt;/li&gt;
&lt;li&gt;Convert universal time to Greenwich sidereal time (GST)&lt;/li&gt;
&lt;li&gt;Convert Greenwich sidereal time to local sidereal time (LST)&lt;/li&gt;
&lt;li&gt;Convert right ascension to hour angle&lt;/li&gt;
&lt;li&gt;Convert equatorial to horizontal coordinates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are now going to explain all the new terms and why these steps are necessary. Also, it will be the opportunity to see how to do so in Ruby, using the standard library or not.&lt;/p&gt;
&lt;h2&gt;
  
  
  LCT to UT
&lt;/h2&gt;

&lt;p&gt;Local civil time, or LCT, is the time from an observer's watch. It is the legal time from their current time zone.&lt;/p&gt;

&lt;p&gt;Universal time, or UT, used to be also knows as the Greenwich mean time, or GMT. It is the solar mean time at The Royal Observatory in Greenwich, London. The solar mean time is just a way to define time with days having the same amount of time. Before time zones, it used to be the way to define time in geographical areas such as cities or regions.&lt;/p&gt;

&lt;p&gt;Converting LCT to UT is actually just a step towards having the local sidereal time, which will be defined below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# It is 10:10:05 pm on June 26th, 2022, in Paris, France&lt;/span&gt;

&lt;span class="n"&gt;local_civil_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"+02:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2022-06-26 03:10:05 +0200&lt;/span&gt;

&lt;span class="n"&gt;universal_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_civil_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2022-06-26 01:10:05 UTC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  UT to GST
&lt;/h2&gt;

&lt;p&gt;Sidereal time is a time scale that is based on Earth's rate of rotation measured relative to fixed stars. Because of Earth's revolution around the Sun, it takes a few minutes longer to have the Sun at culmination than a distant star.&lt;/p&gt;

&lt;p&gt;As equatorial coordinates are a way to define the position of an object regardless of the observer's position or time, it is linked to the apparent motion of stars, on the one of the Sun. Our usual time is based of the motion of the Sun, therefore we need to have a conversion of units.&lt;/p&gt;

&lt;p&gt;Converting UT to GST is a multiple steps process with constants defined in &lt;em&gt;Celestial Calculations&lt;/em&gt; by J. L. Lawrence in chapter 3.10 (see Ruby example below). It also requires calculating the universal time's associated Julian day number. In the previous posts of this series if discovered that a custom implementation of the Julian day calculation fast more performant than the one provided by the Ruby standard library. However, for the sake of simplicity in this post, we will use &lt;code&gt;Date#ajd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's calculate &lt;code&gt;gst&lt;/code&gt;, the Greenwich sidereal time, from a defined universal time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bigdecimal"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"date"&lt;/span&gt;

&lt;span class="n"&gt;universal_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"+02:00"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;
&lt;span class="n"&gt;jd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;universal_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ajd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="n"&gt;jd0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;universal_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ajd&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jd&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;jd0&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jd0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2415020"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"36525"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"6.6460656"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2400.051262"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"0.00002581"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;universal_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;year&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"0.0657098"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="n"&gt;ut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;universal_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
       &lt;span class="n"&gt;universal_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"60"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
       &lt;span class="n"&gt;universal_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sec&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"3600"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"1.002738"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ut&lt;/span&gt;

&lt;span class="n"&gt;gst&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;negative?&lt;/span&gt;
&lt;span class="n"&gt;gst&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gst&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;

&lt;span class="n"&gt;gst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 19.444822123178245&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GST to LST
&lt;/h2&gt;

&lt;p&gt;As GST defines the sidereal time at Greenwich, we need to convert if to the observer's local sidereal time, or LST, in order to take into account the observer's longitude.&lt;/p&gt;

&lt;p&gt;To convert GST to LST, we only need to convert GST to a decimal format (already the case in the Ruby example above) and add a time zone adjustment defined by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adjustment = (observer's longitude in decimal degrees) / 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take the example of an observer at the beautiful beach of Étretat, France.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"14.496844123178246"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"0.20271537957527094"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;adjustment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
&lt;span class="n"&gt;lst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gst&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;adjustment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 19.4583364818166&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Right ascension to hour angle
&lt;/h2&gt;

&lt;p&gt;Converting the right ascension to hour angle is the final step to link the observer's longitude and the right ascension.&lt;/p&gt;

&lt;p&gt;The hour angle is a measure of how long it has been since the object crossed an observer's meridian. It varies both with time of day (relative to a sidereal location, explaining why we need the LST) and an observer's location.&lt;/p&gt;

&lt;p&gt;It is a time measurement, while the right ascension is an angular measurement, but both can be expressed in hour-minute-second (HMS) format. Therefore, the formula that links them is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hour_angle  = lst - right_ascension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On June 26th, 2022, at 3:10:05 AM, Saturn's right ascension was Right 21h 49m 08.6s. We can compute in Ruby the hour angle with the following algorithm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;lst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"19.4583364818166"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;
&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;49&lt;/span&gt;
&lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;8.6&lt;/span&gt;

&lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lst&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="no"&gt;Rational&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="no"&gt;Rational&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hour_angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;negative?&lt;/span&gt;
&lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;

&lt;span class="n"&gt;hour_angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 21.63928092626104&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Equatorial to horizontal coordinates
&lt;/h2&gt;

&lt;p&gt;The final step, having horizontal coordinates to be able, as an observer, to detect the position of the object, on the celestial sphere.&lt;/p&gt;

&lt;p&gt;Not much to say here as converting these two coordinates systems is actually essentially trigonometry.&lt;br&gt;
We have all the data we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;declination&lt;/li&gt;
&lt;li&gt;hour angle&lt;/li&gt;
&lt;li&gt;latitude&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing to keep in mind is that trigonometry functions work with angles in radians. Here, we are mostly manipulating angles in HMS format or degrees, so we will need to convert degrees into radians each time we'll use cosine or sine functions.&lt;/p&gt;

&lt;p&gt;Let's keep our previous data. Note that Saturn's declination associated to our data was -14° 26' 57.4".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;declination_degree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;
&lt;span class="n"&gt;declination_minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;
&lt;span class="n"&gt;declination_second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;57.4&lt;/span&gt;
&lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"21.63928092626104"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"49.70911954641343"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;to_radians&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;
&lt;span class="n"&gt;to_degrees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PI&lt;/span&gt;
&lt;span class="n"&gt;hour_angle_degree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hour_angle&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
&lt;span class="n"&gt;hour_angle_radians&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hour_angle_degree&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;to_radians&lt;/span&gt;
&lt;span class="n"&gt;declination_decimal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;declination_degree&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="n"&gt;declination_minute&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;60.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="n"&gt;declination_second&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;3600.0&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="n"&gt;declination_radians&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;declination_decimal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;to_radians&lt;/span&gt;
&lt;span class="n"&gt;latitude_radians&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;to_radians&lt;/span&gt;
&lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;declination_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
       &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latitude_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
       &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;declination_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
       &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latitude_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
       &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour_angle_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;to_degrees&lt;/span&gt;
&lt;span class="n"&gt;h_radians&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;to_radians&lt;/span&gt;
&lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;declination_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
       &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latitude_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
       &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latitude_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h_radians&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;to_degrees&lt;/span&gt;
&lt;span class="n"&gt;sin_hour_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour_angle_radians&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sin_hour_angle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positive?&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 19.490674347775652&lt;/span&gt;

&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 143.30560194994217&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;On June 26th, 2022, at 03:10:05 AM, at Étretat, France, planet Saturn was visible in the night sky by looking 19° above the horizon and 143° from the North cardinal point.&lt;/p&gt;

&lt;p&gt;I made a &lt;a href="https://gist.github.com/rhannequin/8ab3166aabe9310ca97be54328e2f493" rel="noopener noreferrer"&gt;public gist&lt;/a&gt; to sum up all our algorithm. Note that this is only an example gist, it is not optimized nor considered as good code. But it works.&lt;/p&gt;

&lt;p&gt;Of course this algorithm only works when we have fixed equatorial coordinates. Stars, for example, have "fixed" equatorial coordinates as they are so far from us that their motion is barely perceivable. Planets however are very much closer to us, their position in the Solar System changes all the time. From one day to another, Saturn's position would have changed a little bit from our point of view, and its equatorial coordinates would have been slightly different, so would have its horizontal coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next
&lt;/h2&gt;

&lt;p&gt;This algorithm works and the results are really close to what I compared with several simulation software. However it doesn't include multiple corrections that we'll take time to explain in future posts, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;atmospheric refraction, present especially with objects close to the horizon, like planets&lt;/li&gt;
&lt;li&gt;precession, which slowly changes the celestial poles positions&lt;/li&gt;
&lt;li&gt;nutation, which is another phenomenon that changes the celestial poles positions as well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, the final goal is still to build a nice and maintainable Ruby library. The current algorithm will need to be improved and developed with Ruby objects, ready to welcome future features.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>computing</category>
    </item>
    <item>
      <title>Astronomical computing – Ep 4 - Julian day</title>
      <dc:creator>Rémy Hannequin</dc:creator>
      <pubDate>Mon, 23 May 2022 12:11:53 +0000</pubDate>
      <link>https://dev.to/rhannequin/astronomical-computing-ep-4-julian-day-97n</link>
      <guid>https://dev.to/rhannequin/astronomical-computing-ep-4-julian-day-97n</guid>
      <description>&lt;p&gt;Now that we are managing angles in degrees or radians, we have one of the elementary blocks of the library.&lt;/p&gt;

&lt;p&gt;One other important notion is time. Time is complicated, we are going to talk about it a lot in this series. But first, we can tackle a time notion in astronomy that is going to follow us all the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Julian day number
&lt;/h2&gt;

&lt;p&gt;Most of us now use the &lt;em&gt;Gregorian calendar&lt;/em&gt;&lt;sup&gt;[1]&lt;/sup&gt; that was introduced on October 1582 and starts on the 15th of October, 1582. Before this day, the Julian Calendar was used. They are both very similar calendars, but they space leap years differently, which makes the Gregorian calendar more accurate regarding the real motion of the Earth around the Sun.&lt;/p&gt;

&lt;p&gt;Other calendars still exist, like the Hebrew calendar, the Hijri calendar. Some existed but are not used anymore, like the French Revolutionary calendar or the Maya calendar.&lt;/p&gt;

&lt;p&gt;We didn't talk about leap years, time zones or leap seconds but we can understand from here that time is a complicated, if not ambiguous, notion to grasp. While ambiguity is not something that we want to deal with in astronomy and science in general.&lt;/p&gt;

&lt;p&gt;Therefore, the &lt;em&gt;Julian day&lt;/em&gt;&lt;sup&gt;[2]&lt;/sup&gt; was introduced by Joseph Scaliger in 1583 to manipulate time as a universal entity. Its definition is the number of days that have elapsed since noon at Greenwich, England, on January 1, 4713 BC. Usually, the Julian day is written as a decimal number, with the decimal part being the fraction of the day that passed. The Julian day is the Julian day number plus the fractional day.&lt;/p&gt;

&lt;p&gt;Using the Julian day is very useful in astronomical calculations, especially when comparing two different dates, in order to know the time that separates them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting a date and time to a Julian day
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Algorithm
&lt;/h3&gt;

&lt;p&gt;In &lt;em&gt;Astronomical Algorithms&lt;/em&gt;, Jean Meeus details a simple formula&lt;sup&gt;[3]&lt;/sup&gt; to calculate the Julian day associated to a date. The following method works for any positive Julian day, which means any date starting from January 1, 4713 BC.&lt;/p&gt;

&lt;p&gt;Given a date, sliced in year (&lt;code&gt;Y&lt;/code&gt;), number of the month (&lt;code&gt;M&lt;/code&gt;), day of the month (&lt;code&gt;D&lt;/code&gt;), hour (&lt;code&gt;h&lt;/code&gt;), minute (&lt;code&gt;m&lt;/code&gt;) and second (&lt;code&gt;s&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;M &amp;gt; 2&lt;/code&gt;, then &lt;code&gt;Y&lt;/code&gt; and &lt;code&gt;M&lt;/code&gt; stays the same.&lt;br&gt;
If &lt;code&gt;M&lt;/code&gt; equals &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt;, then &lt;code&gt;Y&lt;/code&gt; equals &lt;code&gt;Y - 1&lt;/code&gt; and &lt;code&gt;M&lt;/code&gt; equals &lt;code&gt;M + 12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the date is on the Gregorian calendar, meaning equal of greater than 15th October 1582, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A = INT(Y / 100)
B = 2 - A + INT(A/4)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;INT()&lt;/code&gt; being a truncation function so that it represents the whole part of the number.&lt;/p&gt;

&lt;p&gt;If the date is on the Julian calendar, then &lt;code&gt;A&lt;/code&gt; is not necessary and &lt;code&gt;B&lt;/code&gt; equals &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, the Julian day number is defined by the following formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INT(365.25 (Y + 4716)) + INT(30.6001 (M + 1)) + D + B - 1524.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Julian day, which is the Julian day number plus the fraction of the day that passed, is the same formula, replacing &lt;code&gt;D&lt;/code&gt; as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D = D + h / 24 + m / 1440 + s / 86400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One notion to keep in mind is that the Julian day dating started at noon, which means a time at noon will give a whole Julian day, while any other time will give a Julian day with decimals.&lt;/p&gt;

&lt;p&gt;Also, the dating started at noon at Greenwich, so any time defined on another time zone than UTC will need to be converted into UTC before being converted into a Julian day. But we will talk more about time zones in future posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation in Ruby
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;DateTime#jd&lt;/code&gt; and &lt;code&gt;DateTime#ajd&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The Ruby standard library&lt;sup&gt;[4]&lt;/sup&gt; provides two methods to compute the Julian day number and the Julian day.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DateTime#jd&lt;/code&gt; returns the corresponding Julian day number of a &lt;code&gt;date_time&lt;/code&gt;. It is the "chronological" Julian day number, which means if suffers from half a day advance from the original Julian day number. Also, it doesn't include the fraction of time passed in the day. To have the original Julian day from &lt;code&gt;DateTime#jd&lt;/code&gt;, it is required to add a few instructions to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;jd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;24.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1440.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2459671.0625&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DateTime#ajd&lt;/code&gt; returns the "astronomical" Julian day, which is the original one. However, it returns it as a fractional number, so it needs to be converted to be displayed in a way that is understandable to the human eye:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;ajd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ajd&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; (39354737/16)&lt;/span&gt;

&lt;span class="n"&gt;ajd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2459671.0625&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Using the most performant method
&lt;/h4&gt;

&lt;p&gt;Even if it is tempting to use something ready out of the box, what the Ruby standard library provides us is not perfect. It needs complementary work, or conversion, and this may sometimes reflect reduced performance.&lt;/p&gt;

&lt;p&gt;I developed a simple benchmark to compare &lt;code&gt;DateTime#jd&lt;/code&gt; with adjustments, &lt;code&gt;DateTime#ajd&lt;/code&gt; with conversion, and the custom algorithm provided by Jean Meeus.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bigdecimal"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"date"&lt;/span&gt;

&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1957&lt;/span&gt;
&lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;
&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;
&lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;

&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000_000&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DateTime#jd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;jd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;24.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1440.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;86400.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DateTime#ajd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;DateTime&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ajd&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Meeus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1582&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1582&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1582&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;julian_day_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;365.25&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4716&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;30.6001&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1524.5&lt;/span&gt;
      &lt;span class="n"&gt;time_of_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;24.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1440.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;86400.0&lt;/span&gt;

      &lt;span class="n"&gt;julian_day_number&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;time_of_day&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#                user       system     total      real&lt;/span&gt;
&lt;span class="c1"&gt;# DateTime#jd    2.766334   0.003965   2.770299   (2.770522)&lt;/span&gt;
&lt;span class="c1"&gt;# DateTime#ajd   5.054064   0.000000   5.054064   (5.054338)&lt;/span&gt;
&lt;span class="c1"&gt;# Meeus          1.978400   0.000000   1.978400   (1.978430)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also ran this benchmark on multiple computers and Ruby versions, with very similar results. Thanks to my teammates at &lt;a href="https://uk.getaround.com/team" rel="noopener noreferrer"&gt;Getaround&lt;/a&gt; for their help.&lt;/p&gt;

&lt;p&gt;The custom algorithm from Jean Meeus is the most performant, it is the one I am going to implement in &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;astronoby&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting a Julian day to a date and time
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Algorithm
&lt;/h3&gt;

&lt;p&gt;Still from Jean Meeus' book&lt;sup&gt;[5]&lt;/sup&gt;, given &lt;code&gt;J&lt;/code&gt; the Julian day augmented by &lt;code&gt;0.5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Z = INT(J)&lt;/code&gt; and &lt;code&gt;F = DEC(J)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;DEC()&lt;/code&gt; being a truncation function so that it represents the decimal part of the number.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Z &amp;lt; 2299161&lt;/code&gt;, then we define &lt;code&gt;A&lt;/code&gt; as the same value as &lt;code&gt;Z&lt;/code&gt;. If &lt;code&gt;Z&lt;/code&gt; is greater or equal to &lt;code&gt;2299161&lt;/code&gt;, then we define &lt;code&gt;A&lt;/code&gt; as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;α = INT((Z - 1867216.25) / 36524.25)
A = Z + 1 + α - INT(α / 4)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now define &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt; as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;B = A + 1524
C = INT((B - 122.1) / 365.25)
D = INT(265.25 * C)
E = INT((B - D) / 30.6001)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we have the day of the month, as a decimal number, equal to &lt;code&gt;B - D - INT(30.6001 * E) + F&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The number of the month (&lt;code&gt;M&lt;/code&gt;) is equal to &lt;code&gt;E - 1&lt;/code&gt; if &lt;code&gt;E &amp;lt; 14&lt;/code&gt;, and equal to &lt;code&gt;E - 13&lt;/code&gt; if &lt;code&gt;E&lt;/code&gt; is equal to &lt;code&gt;14&lt;/code&gt; or &lt;code&gt;15&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The year number is equal to &lt;code&gt;C - 4716&lt;/code&gt; if &lt;code&gt;M &amp;gt; 2&lt;/code&gt;, and equal to &lt;code&gt;C - 4715&lt;/code&gt; if &lt;code&gt;M&lt;/code&gt; is equal to &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation in Ruby
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;DateTime::jd&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Similarly, &lt;code&gt;DateTime&lt;/code&gt; provides a class method &lt;code&gt;jd&lt;/code&gt; to instantiate &lt;code&gt;Datetime&lt;/code&gt; from a chronological Julian day. Adding &lt;code&gt;0.5&lt;/code&gt; makes it astronomical, and will result into the accurate date and time associated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2436116.31&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;DateTime: 1957-10-04T19:26:24+00:00 ((2436116j,69984s,4828n),+0s,2299161j)&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Using the most performant method
&lt;/h4&gt;

&lt;p&gt;I developed a second benchmark to compare &lt;code&gt;DateTime#jd&lt;/code&gt; with a custom implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bigdecimal"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"date"&lt;/span&gt;

&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BigDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2436116.31"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DateTime::jd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Meeus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;julian_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
      &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;julian_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;
      &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;julian_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;julian_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2299161&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1867216.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;36524.25&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1524&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;122.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;365.25&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;
      &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;365.25&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;
      &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;30.6001&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;

      &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;30.6001&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;
      &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;
      &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4716&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4715&lt;/span&gt;

      &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
      &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
      &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;

      &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#               user       system     total      real&lt;/span&gt;
&lt;span class="c1"&gt;# DateTime::jd  6.562874   0.000000   6.562874   (6.563703)&lt;/span&gt;
&lt;span class="c1"&gt;# Meeus         6.316613   0.000000   6.316613   (6.317376)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, the custom implementation is only slightly more performant than the standard one. I would normally use the standard implementation for such a small performance difference, however the custom one will let me have access to each part of the date time (year, month, ...) without having to extract it afterwards, and it will probably be useful in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I am really satisfied with this research as one of my goal with this series is to both implement and explain astronomical calculations. By having custom algorithms being as performant or more performant than those implemented in the standard library, it is a logical choice to implement the custom ones and therefore to have an explanatory source code.&lt;/p&gt;

&lt;p&gt;However, it takes a lot of time to translate my research into code, and then write about the theory and the Ruby implementation. It already takes me quite some time to understand these notions, try them and add them to &lt;a href="https://github.com/rhannequin/astronoby" rel="noopener noreferrer"&gt;astronoby&lt;/a&gt;. Duplicating the Ruby code into articles is going to take too much time, and will probably be a bit difficult to read.&lt;br&gt;
In future articles, I will try to find something in the middle, with Ruby code only when it's appropriate and necessary.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy hacking&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[1]&lt;/sup&gt;&lt;/a&gt; &lt;a href="https://en.wikipedia.org/wiki/Gregorian_calendar" rel="noopener noreferrer"&gt;Gregorian calendar&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[2]&lt;/sup&gt;&lt;/a&gt; &lt;a href="https://en.wikipedia.org/wiki/Julian_day" rel="noopener noreferrer"&gt;Julian day&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[3]&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;Astronomical Algorithms&lt;/em&gt; by Jean Meeus, Chapter 7, page 59&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[4]&lt;/sup&gt;&lt;/a&gt; &lt;a href="https://ruby-doc.org/stdlib-2.7.6/libdoc/date/rdoc/DateTime.html" rel="noopener noreferrer"&gt;Ruby &lt;code&gt;DateTime&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;&lt;sup&gt;[5]&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;Astronomical Algorithms&lt;/em&gt; by Jean Meeus, Chapter 7, page 63&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>astronomy</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
