So far SkySutra has been about tonight. Part 9 is about the year ahead, and about the month as the Moon counts it. You build a calendar of what's coming in the sky, from eclipses and meteor showers to oppositions and the Moon passing the planets. You work out the 30 lunar days, the tithis, to the minute. Then you write short, sourced stories in Markdown, and draw the two zodiacs on the sky chart to show why a sign in a newspaper and a sign in a panchang differ.
How Part 9 works
| Lesson | You build | New in APEX |
|---|---|---|
| 9.1 A sky calendar | Sky Calendar (page 60): a year of sky events, with what's coming up in the next 30 days | Calendar region |
| 9.2 Lunar days through the month | Lunar Days (page 62): the tithi now, day by day at sunrise, and when each one starts and ends | — |
| 9.3 Lore pages | Lore (page 70) and Lore Panel (page 71); stories on each star's page | Markdown Editor item; APEX_MARKDOWN |
| 9.4 Why the signs disagree | Signs (page 64): every body in both zodiacs, and both drawn on the sky chart | A Checkbox plugin attribute |
Before you start
You need Part 8 done. Download and run, in this order:
skysutra_calendar.sql: the published eclipses and meteor showers, tableSS_TITHIS, packageSS_CALENDAR, and viewsSS_CALENDAR_V,SS_TITHIS_VandSS_PANCHANG_V.skysutra_lore.sql: packageSS_LORE_APIand six panels.skysutra_signs.sql: viewSS_SIGNS_V.- The new
skysutra_chart.sql, plusskysutra-chart.jsandskysutra.cssin Static Application Files.
Then work out the year ahead, in SQL Commands:
begin
ss_calendar.refresh_tithis(date '2026-10-01', 460); -- under a second
ss_calendar.refresh_planet_events(date '2026-10-01', 460); -- about 17 seconds
ss_almanac.refresh_moon_phases(date '2026-10-01', 460);
commit;
end;
Part 10 runs these every night, so the calendar always reaches a year ahead.
Lesson 9.1 — A sky calendar
Outcome: a calendar of the sky's events with a list of the next 30 days under it.
The question. What's worth going out for this year? Some events can only be looked up. Others SkySutra can work out itself.
| Kind | Where it comes from | Why |
|---|---|---|
| Eclipses | NASA Goddard's eclipse tables | Predicting one needs the Moon's position to a fraction of an arcminute, and where the shadow falls on the Earth. Look it up. |
| Meteor showers | The International Meteor Organization's yearly calendar (2027) and the American Meteor Society (late 2026) | A shower is the Earth crossing a comet's debris. Its peak is measured, year after year, not calculated from first principles. |
| Moon phases | SS_ALMANAC (Part 3) |
Already computed |
| Oppositions, conjunctions with the Sun, greatest elongations | SS_CALENDAR |
Angles between the planets and the Sun, which SS_PLANETS gives |
| The Moon near a planet | SS_CALENDAR |
The Moon's longitude passing a planet's |
Every row in SS_SKY_EVENTS has a source. The published rows name their table, and the computed ones say "SkySutra (computed)".
Finding a moment: crossings and turning points
SS_CALENDAR finds events two ways, and both come from Part 3.
A crossing. An opposition is the moment the planet's ecliptic longitude is exactly 180° from the Sun's. from_sun(code, t) gives that angle, from −180° to 180°. Step through the year a day at a time. When the angle changes sign between two days, the crossing is between them. Halve that day 24 times, which pins it down to about five milliseconds:
for i in 1 .. 24 loop
mid := lo + (hi - lo) / 2;
if sign(norm180(f(p_what, p_code, mid) - p_target)) = l_lo_sign then lo := mid; else hi := mid; end if;
end loop;
Conjunctions with the Sun are the same search with a target of 0°. For Mercury and Venus, the distance tells you which kind of conjunction it is: closer than 1 AU means inferior (between us and the Sun), farther means superior (behind it).
A turning point. A greatest elongation is the day Mercury or Venus stops pulling away from the Sun. There's no crossing to find, only a maximum. A day is a maximum when it beats the days either side. Then a ternary search narrows it: compare the angle at the two points a third of the way in from each end, keep the two-thirds that hold the larger one, and repeat 30 times.
The Moon passing a planet is a crossing of the Moon's longitude over the planet's, in six-hour steps (the Moon gains about 3° in that time). Only passes closer than 5° and more than 20° from the Sun go in the calendar. A wide pass, or one lost in twilight, isn't worth a trip outside.
How good is it?
The National Astronomical Observatory of Japan publishes the year's planetary phenomena. For 2027, SkySutra's times compare like this:
| Event | NAOJ (UT) | SkySutra (UT) |
|---|---|---|
| Venus at greatest elongation west | Jan 3, 18h, 46°57′ | Jan 3, 18:37, 47.0° |
| Jupiter at opposition | Feb 11, 00:29 | Feb 11, 00:13 |
| Mars at opposition | Feb 19, 15:51 | Feb 19, 15:46 |
| Saturn at opposition | Oct 18, 00:36 | Oct 18, 00:02 |
| Venus at superior conjunction | Aug 11, 23:21 | Aug 11, 23:40 |
All 24 of SkySutra's planet events for 2027 land within 35 minutes of NAOJ's (which gives greatest elongations to the hour). That's the 2′ accuracy of Schlyter's method from Lesson 3.6: a planet near opposition moves about a degree a day against the Sun, so 2′ is about half an hour. For a calendar, it's plenty.
It also caught a real one: "Moon near Jupiter, 0.2°" on Oct 6, 2026. From parts of the world the Moon passed right in front of Jupiter that morning (an occultation), and astronomy magazines covered it.
Build: the Sky Calendar
-
Create Page › Calendar: 60, Sky Calendar, table
SS_CALENDAR_V, display column TITLE, start STARTS_ON, end ENDS_ON.SS_CALENDAR_Vputs the three sources in one shape: events, Moon phases, and four tithis people keep (both Ekadashis, Purnima and Amavasya, from Lesson 9.2). Its times are already in the signed-in person's home time zone:cast(e.peaks_at at time zone tz.time_zone as date) as starts_onThe Calendar region shows a
DATEexactly as it is, with no time zone. So convert to local time in the view, then cast toDATE. Hand it aTIMESTAMP WITH TIME ZONEinstead and an event at 03:25 UT appears on the wrong day for anyone west of London. -
Region Attributes:
- Primary Key Column
ID. - Show Time Yes.
- CSS Class
CSS_CLASS. The view gives each kind a class (ss-cal-eclipse,ss-cal-shower,ss-cal-opposition,ss-cal-elongation,ss-cal-conjunction,ss-cal-moon,ss-cal-tithi), andskysutra.csscolours them. One CSS variable per class sets the bar, the dot in month view and the dot in list view. - Supplemental Information
&DETAILS.<br><small>Source: &SOURCE.</small>, shown as the event's tooltip.
- Primary Key Column
-
Below it, a Classic Report Coming up:
ss_calendar_vfrom today for 30 days, ordered by time, no pagination, stretched.
Lesson 9.2 — Lunar days through the month
Outcome: a page that says which tithi it is, how long it lasts, and which tithi each of the next 14 days belongs to.
The question. Lesson 3.5 worked out the tithi at any moment: the Moon's lead over the Sun, in 12° steps. A panchang asks two more things. When does this tithi end? And which tithi does today belong to?
When each one starts and ends. ss_calendar.refresh_tithis steps through time three hours at a time and bisects every change of tithi to the second, into SS_TITHIS. Tithis aren't equal. The Moon's speed varies along its orbit, so they run from 20.2 to 26.7 hours (the range for Oct 2026–Dec 2027).
Checked against Drik Panchang: Sharad Purnima 2026 runs 11:55 Oct 25 to 09:41 Oct 26 in New Delhi. SkySutra says 11:57 to 09:42. The Amavasya of Diwali begins at 11:27 on Nov 8 in Hyderabad's panchang, and SkySutra says 11:28. A tithi is the same moment everywhere, so the city only changes the clock.
Which tithi a day belongs to. A panchang gives each day the tithi that is in force at local sunrise. SS_PANCHANG_V does the same for your home place: for each of the next 14 days it finds sunrise with ss_almanac.sun_crossing (Lesson 3.6), then reads the tithi and the Moon's nakshatra at that instant.
Because tithis and days have different lengths, they don't line up one to one:
| Name | What happens | How the view sees it |
|---|---|---|
| Kshaya (lost) tithi | A short tithi starts after one sunrise and ends before the next, so no day is named after it | Tomorrow's tithi is two ahead of today's |
| Vriddhi (extra) tithi | A long tithi spans two sunrises, so two days share its name | Tomorrow's tithi is today's |
case mod(lead(t.tithi) over (order by t.local_date) - t.tithi + 30, 30)
when 0 then 'Continues tomorrow (vriddhi)'
when 2 then ss_vedic.tithi_name(mod(t.tithi, 30) + 1) || ' falls between two sunrises (kshaya)'
end as note
mod(... + 30, 30) handles the wrap from Amavasya (30) back to Shukla Pratipada (1). At Wesley Chapel, Krishna Shashthi is lost between the sunrises of Oct 30 and 31, 2026, and Shukla Tritiya covers both Nov 11 and 12. In New Delhi, sunrise comes at a different moment, so the lost tithi is a different one: Krishna Dwitiya, on Oct 28. That's why a panchang names its city.
The view works everything out when it's queried: about 0.13 s a day, almost all of it finding sunrise. Fourteen days is under 2 seconds, so keep the window short. Part 10 could store it nightly like the rest.
Build: Lunar Days
Create Page › Blank Page: 62, Lunar Days, with three Classic Reports:
- Now, template Value Attribute Pairs - Row: the tithi now, its paksha (waxing or waning half), when it started and when it ends, from
SS_TITHIS_V where is_now = 'Y'. - Day by day: date, vara (weekday), sunrise, tithi at sunrise, the Moon's nakshatra and the note, from
SS_PANCHANG_V. - When each tithi starts and ends: three days back to 30 days ahead from
SS_TITHIS_V, with a "Now" column on the current one.
In Shared Components › Navigation Menu, put Lunar Days (and Signs, from Lesson 9.4) under Sky Calendar. The top menu shows them as a submenu, so twelve pages don't crowd the bar.
Lesson 9.3 — Lore pages
Outcome: short stories about stars, constellations and nakshatras, written in Markdown, each one naming its source, shown on the pages they're about.
The question. SkySutra's tradition panels (Lesson 6.3) give names: Vega is Abhijit, Aldebaran is the junction star of Rohini. The stories behind the names need prose, emphasis, maybe a list. Who writes them, in what, and how do they stay honest?
The decision.
- Markdown in
SS_LORE.BODY_MD. It's readable as plain text, easy to edit, and there's no HTML to sanitize, because the conversion escapes any HTML inside it. - A source on every panel.
SOURCEisNOT NULL. A story without one doesn't go in. - The rule from MODEL.md: a panel says where a story comes from and what people have believed. It never says what a star means for you. SkySutra explains traditions; it doesn't predict.
The six panels to start with:
| Panel | About | Source |
|---|---|---|
| Krittika, where the old lists begin | Nakshatra Krittika (the Pleiades) | Taittiriya Samhita 4.4.10 (Keith, 1914) |
| Rohini, the red one | Nakshatra Rohini (Aldebaran) | Mahabharata, Shalya Parva 35 (Ganguli) |
| The seven sages and Arundhati | Ursa Major (Mizar and Alcor) | Gobhila Grihya Sutra II.3 (Oldenberg, 1892) |
| Dhruva, the boy who would not move | Polaris | Vishnu Purana I.11–12 (Wilson, 1840) |
| Abhijit, the 28th nakshatra | Vega | Atharvaveda 19.7 (Whitney, 1905) |
| Chitra holds the zodiac in place | Spica | Calendar Reform Committee report (1955) |
All the translations named are old enough to be in the public domain. Where scholars disagree (did the Krittika-first lists record the sky of 2300 BCE?), the panel says so.
Showing Markdown
ss_lore_api.panels_html turns the panels about a star, its constellation or its nakshatra into HTML:
l_html := l_html
|| '<article class="ss-lore">'
|| '<h3 class="ss-lore-title">' || apex_escape.html(r.title) || '</h3>'
|| apex_markdown.to_html(r.body_md, p_softbreak => ' ')
|| '<p class="ss-lore-source">Source: ' || apex_escape.html(r.source) || '</p>'
|| '</article>';
apex_markdown.to_htmlescapes HTML written inside the Markdown (its default), so a<script>typed into a panel shows as text.- The title and the source aren't Markdown, so
apex_escape.htmlescapes them. p_softbreak => ' 'treats a line break inside a paragraph as a space, the way every Markdown viewer does. The default turns each one into<br />.
Build: stories on a star's page
On page 31 (Star), add a region Stories, type Dynamic Content, between Tradition and the sky chart. Its PL/SQL returns the HTML:
select s.constellation_id, n.id
into l_constellation_id, l_nakshatra_id
from ss_stars s
join ss_stars_v v on v.id = s.id
left join ss_nakshatras n on n.name = v.nakshatra
where s.id = :P31_ID;
l_html := ss_lore_api.panels_html(:P31_ID, l_constellation_id, l_nakshatra_id);
return coalesce(l_html, '<p class="ss-lore-source">No stories about this star yet.</p>');
Aldebaran now shows Rohini's story, Alcyone shows Krittika's, and Mizar shows the seven sages'.
Build: writing panels
- Create Page › Form: 71, Lore Panel, Modal Dialog, table
SS_LORE. The wizard writes the Automatic Row Processing (DML) process. Lore is plain content, so it needs no package or workflow, unlike observations. - Change
P71_BODY_MDto a Markdown Editor: a text area with a toolbar (bold, italic, lists, links) and a Preview tab. - Delete
P71_EMBEDDING. It's filled in Part 11, not typed. - Make the subject items lists of values: a Popup LOV for the star, Select Lists for the constellation, nakshatra and body. Make
P71_STATUSa Select List with Draft and Published. - Set the page's Authorization Scheme to Administration Rights.
- Create Page › Blank Page: 70, Lore:
- A Dynamic Content region Stories from the sky:
return ss_lore_api.panels_html(p_all => 'Y'); - A Classic Report Manage panels with Administration Rights: title (a link to page 71), what it's about, status and source.
- A Write a Panel button, and a Dialog Closed › Submit Page dynamic action.
- A Dynamic Content region Stories from the sky:
Lesson 9.4 — Why the signs disagree
Outcome: a page that shows each body in both zodiacs, and the sky chart with both drawn on it.
The question. A newspaper says the Sun is in Scorpio on Oct 25. A panchang says it's in Tula (Libra). Both are measuring the same Sun. What's different?
The answer. Where they start counting.
- The tropical zodiac starts at the March equinox, where the Sun crosses the celestial equator going north. It's tied to the seasons. Every astronomy catalog (and Western astrology) uses it.
- The sidereal zodiac starts from the stars. India's official choice, the Lahiri ayanamsa, puts Spica (Chitra) at exactly 180°.
- Precession turns the Earth's axis once in about 26,000 years. The equinox slides west along the ecliptic about 1.4° a century, so the two zodiacs drift apart. Today the gap, the ayanamsa, is 24.23°.
Sidereal longitude = tropical longitude − ayanamsa. SS_AYANAMSA (Lesson 1.7) and ss_vedic.sidereal (Lesson 3.5) have done this all along.
SS_SIGNS_V lists the Sun, the Moon, the planets and Rahu and Ketu with both. Its last row is the check: Spica, from its J2000 catalog position moved to today's equinox, should be at sidereal 180°. It comes out at 179.99°.
Late on Oct 24, 2026 (US time), the Sun is at tropical 211.7° (Scorpio) and sidereal 187.5° (Tula). Saturn is in Aries one way and Meena (Pisces) the other. A body in the first 24° of a tropical sign is in the previous sidereal sign; one in the last 6° is in the same sign both ways. Tonight the Sun, the Moon and every planet fall in the first 24°, so all of them are a sign "earlier" sidereally. Rahu and Ketu, 26.4° into theirs, stay put: Aquarius and Kumbha, Leo and Simha.
The chart, with both zodiacs
The sky chart gets an optional layer. When the region asks for it, ss_chart.sky_json adds:
ecliptic: the ecliptic every 2°, as altitude and azimuth, below the horizon toosigns: for each sign, where it starts and its middle, both tropical (kindT) and sidereal (S), only those above the horizon
l_lon := (g.seq - 1) * 30
+ case when w in (2, 4) then 15 else 0 end -- the middle, for the name
+ case when w > 2 then l_ayan else 0 end; -- sidereal: the ayanamsa on
ss_coord.ecliptic_to_equatorial(ss_time.norm360(l_lon), 0, p_at, l_ra, l_dec);
ss_coord.to_horizon(l_ra, l_dec, p_at, l_place.latitude, l_place.longitude, l_alt, l_az);
skysutra-chart.js draws the ecliptic as a dashed line. It lifts the pen wherever the points dip below the horizon, so the line never crosses the disc. Then it marks each sign's start with a dot and writes its name: tropical names in blue above the line, sidereal names in saffron below it. You can see the two zodiacs a sign apart along the same path, with the Moon and the planets sitting on it.
Build: the plugin attribute and the Signs page
- Shared Components › Plug-ins › Sky Chart › Custom Attributes › Add Attribute: static ID
zodiac, label Show Zodiac, type Checkbox, default N.ss_chart.render_regionreads it withp_region.attributes.get_varchar2('zodiac')and passes it on tosky_json. Regions that don't set it (page 1, page 31) look as before. - Create Page › Blank Page: 64, Signs:
- Two zodiacs (Static Content): four short paragraphs, the explanation above.
- Where everything is now (Classic Report on
SS_SIGNS_V), with the day's ayanamsa in its header from a hidden itemP64_AYANAMSA(a Before Header computation,to_char(ss_vedic.ayanamsa(systimestamp), 'fm990.0000')). - The signs in your sky: a Sky Chart region with Show Zodiac checked and the faintest star at 3.5, so the labels have room.
Checkpoint
SkySutra now has a sky calendar for the year, the lunar month day by day, stories with their sources, and both zodiacs on one chart.
Test it
- Sky Calendar: November 2026 shows the Leonids (Nov 17), Uranus at opposition (Nov 25), full and new Moons, and Ekadashi, Purnima and Amavasya. Hover an eclipse in February 2027 for its details and source. Switch to the list view.
- Lunar Days: today's tithi with its end time, and a Day by day list with at least one kshaya or vriddhi note in most fortnights. Compare a Purnima with Drik Panchang for your city.
- Stars › Aldebaran: the Stories region tells Rohini's story, with its source.
- Sign in as yourself (an administrator): Lore › Write a Panel, try the Markdown Editor's Preview, save as Draft. It appears in Manage panels but not in Stories until you publish it. Type
<b>hi</b>into the story: it shows as text, not bold. - Signs: Spica's sidereal longitude is 179.99°, and the chart shows the dashed ecliptic with blue and saffron names about a sign apart.
Files
| File | Used in |
|---|---|
skysutra_calendar.sql |
9.1, 9.2 (SS_SKY_EVENTS data, SS_TITHIS, SS_CALENDAR, SS_CALENDAR_V, SS_TITHIS_V, SS_PANCHANG_V) |
skysutra_lore.sql |
9.3 (SS_LORE_API, six panels) |
skysutra_signs.sql |
9.4 (SS_SIGNS_V) |
skysutra_chart.sql, skysutra-chart.js, skysutra.css |
9.3, 9.4 (the zodiac layer, lore styles) |
Exercises
- Add a Search Configuration for
SS_LORE(title and body) and add it to the Search page from Lesson 6.4, so "Arundhati" finds the seven sages. - Give the calendar a Kind filter: a checkbox group above the region, and
where kind in (...)with the item in Page Items to Submit. - Show the Moon's nakshatra changes on the Lunar Days page, the way tithis are shown: when each starts and ends.
- Add the Moon near the bright stars (Regulus, Spica, Antares, Aldebaran) to
refresh_planet_events. All four are close enough to the ecliptic for the Moon to cover them. - Stretch: a second ayanamsa. Add the Raman or Krishnamurti values to
SS_AYANAMSAand let the Signs page choose. How many bodies change sign?
Review questions
Try each one before you open it. The graded quiz is at the end of the page.
1Why are eclipses and meteor showers loaded from published tables, while oppositions are computed?
Eclipse paths and shower peaks need data SkySutra doesn't have — precise lunar positions and the Earth's shadow, or years of meteor counts. Oppositions are only angles that SS_PLANETS already gives
2How do you find the moment of an opposition?
Step a day at a time until the planet–Sun angle passes 180°, then bisect that day
3Why does a greatest elongation need a different search?
It's a maximum, not a crossing: there's no sign change to bisect, so a ternary search narrows it
4Why does SS_CALENDAR_V convert to the home time zone and cast to DATE?
The Calendar region shows a DATE as it is; a UTC time would land some events on the wrong day
5How long is a tithi?
From about 20 to about 27 hours: the Moon's speed along its orbit varies
6Which tithi does a day belong to in a panchang?
The one in force at local sunrise
7What is a kshaya tithi?
One that starts and ends between two sunrises, so no day is named after it
8Why is the Markdown body safe to show?
apex_markdown.to_html escapes HTML inside it by default; the title and source are escaped with apex_escape.html
9Why does every lore panel have a source, and what will a panel never say?
So the reader can check it; it never says what a star means for a person
10What is the ayanamsa, and why does it grow?
The gap between the tropical and sidereal zodiacs; precession moves the equinox about 1.4° a century
Downloads for Part 9
Everything this part uses. The SQL files are plain text: open one in any editor before you run it.
- Part 9 kitskysutra-part-9.zipEvery file above in one zip, with a README
- Calendarskysutra_calendar.sqlLessons 9.1 and 9.2: events, tithis and the panchang
- Loreskysutra_lore.sqlLesson 9.3: SS_LORE_API and six sourced panels
- Signsskysutra_signs.sqlLesson 9.4: SS_SIGNS_V
- Sky chartskysutra_chart.sqlLesson 9.4: the zodiac layer
- Sky chart drawingskysutra-chart.jsLesson 9.4
- Shared stylesskysutra.cssLessons 9.3 and 9.4
Part 9 quiz
5 questions. Get 4 right to pass. You can retake it as often as you like.
