# Stop hand-coding the Japanese Rokuyo calendar: LLM-generated lunar logic silently breaks

> Source: <https://dev.to/shirabedev/stop-hand-coding-the-japanese-rokuyo-calendar-llm-generated-lunar-logic-silently-breaks-3pie>
> Published: 2026-06-06 01:37:13+00:00

Originally published in Japanese on

[Qiita]. This is the English edition.

`curl`

/ `fetch`

/ `requests`

.**Rokuyo (六曜)** is a six-day cycle (Taian / Tomobiki / Sensho / Senbu / Butsumetsu / Shakko) still used across Japan to decide auspicious dates — weddings, funerals, ground-breakings, store openings. If you build anything for the Japanese market that recommends or scores *dates*, you will eventually need it. It looks trivial. It is not.

Ask a coding assistant for "a function that tells me whether a given date is Taian" and you'll usually get something shaped like this:

```
// The "naive Rokuyo" an LLM tends to propose
function getRokuyo(gregorianDate: Date): string {
  // (1) Convert Gregorian -> lunar (year / month / day)
  const lunar = convertToLunar(gregorianDate);   // <- this is where accidents live

  // (2) Rokuyo = (lunar month + lunar day) mod 6
  const index = (lunar.month + lunar.day) % 6;
  return ["Taian", "Shakko", "Sensho", "Tomobiki", "Senbu", "Butsumetsu"][index];
}
```

Step (2) is genuinely correct — that mapping is right. The bug is **always** in (1), `convertToLunar`

.

`convertToLunar`

is written (all wrong)
`lunar-javascript`

.This is the **silent-failure** class — the most common kind of bug in the era of letting an LLM write the code.

The Rokuyo rule `(lunar month + lunar day) mod 6`

is deterministic and always correct *once the lunar date is right*. The hard part is the lunar date. Japan's old calendar (Tenpō) requires:

Doing this properly means computing **solar and lunar ecliptic longitude at astronomical precision** (VSOP87 and friends). Use an approximation and, on days where the new moon falls near midnight, you shift by one day — and **every subsequent lunar date that year dominoes by one day**.

A widely cited reference implementation is **QREKI** (by Hideaki Takano): hundreds of lines of AWK computing apparent ecliptic longitudes via series expansion. Hand-porting that is, both in volume and in principle, **not the kind of task you should hand to an LLM**.

Drop the self-implementation and hand it to an API designed for AI agents — you win on accuracy, maintenance cost, and ease of AI integration. The examples below use [Shirabe Calendar API](https://shirabe.dev). The free tier is **10,000 calls/month** and you can try it **without an API key**. The `fetch`

/ `requests`

examples below still pass `X-API-Key`

— it's optional, used to track usage and raise limits.

``` php
# One date -> Rokuyo, rekichu (auspicious days), kanshi, solar term, all in one response
curl https://shirabe.dev/api/v1/calendar/2026-04-15
```

The JSON looks like:

```
{
  "date": "2026-04-15",
  "rokuyo": {
    "name": "大安",
    "reading": "Taian",
    "timeSlots": { "morning": "good", "noon": "good", "afternoon": "good", "evening": "good" }
  },
  "rekichu": [
    { "name": "一粒万倍日", "type": "auspicious" }
  ],
  "context": {
    "wedding": { "judgment": "excellent", "score": 9,
      "note": "Taian x Ichiryu-manbai-bi. An excellent day for a wedding." }
  },
  "summary": "Reiwa 8 (2026)-04-15 (Wed) Taian / Ichiryu-manbai-bi. Excellent for weddings and openings."
}
```

The key fields are ** context and summary**. Instead of a bare "Taian" label, you get a per-purpose judgment (8 categories, each scored 1–10) and a one-line summary. An AI agent can drop that

`summary`

straight into its answer to the user.

``` js
const res = await fetch(
  "https://shirabe.dev/api/v1/calendar/2026-04-15",
  { headers: { "X-API-Key": process.env.SHIRABE_API_KEY! } }
);
const data = await res.json();
console.log(data.rokuyo.name);   // "大安" (Taian)
console.log(data.summary);       // "Reiwa 8 (2026)-04-15 (Wed) Taian ..."
python
import os, requests

r = requests.get(
    "https://shirabe.dev/api/v1/calendar/2026-04-15",
    headers={"X-API-Key": os.environ["SHIRABE_API_KEY"]},
    timeout=10,
)
print(r.json()["rokuyo"]["name"])
```

You're not limited to a single date.

```
# All Taian/Tomobiki days in April (up to 93 days)
curl "https://shirabe.dev/api/v1/calendar/range?start=2026-04-01&end=2026-04-30&filter_rokuyo=大安,友引"

# Top 5 best wedding days from April to December, by score
curl "https://shirabe.dev/api/v1/calendar/best-days?purpose=wedding&start=2026-04-01&end=2026-12-31&limit=5"
```

`best-days`

is built so an **AI agent can externalize the judgment itself**. Pass a date range and a purpose (

`wedding`

/ `moving`

/ `business`

and 8 categories total) and you get a ranked list of top-scoring days with the reasoning (e.g. Taian × Tensha-bi).Publish an **OpenAPI 3.1 spec** and this kind of API can be called in one shot from ChatGPT GPTs Actions, Claude tool use, Gemini function calling, LangChain, or LlamaIndex. Shirabe serves it directly at [https://shirabe.dev/openapi.yaml](https://shirabe.dev/openapi.yaml).

In the GPT Builder, "Create new action" → paste the OpenAPI URL into Import URL:

```
https://shirabe.dev/openapi.yaml
```

Choose API Key auth (header name `X-API-Key`

). That's all it takes for a custom GPT to call Shirabe automatically.

Feed the same URL to an OpenAPI loader and it's turned into tools. `operationId`

becomes the function name by design, so no glue code is needed.

| Dimension | Self-implementation / LLM-generated code | Japanese calendar API (Shirabe etc.) |
|---|---|---|
| Lunar accuracy | Approximations slip by a day at new-moon boundaries | Astronomical precision |
| Rekichu coverage | Implementing the ~13 auspicious-day types is a separate big job | 13+ types out of the box |
| Per-purpose judgment | Hard to spec and to build | Provided via `context.wedding.score` etc. |
| Maintenance | Versioning the saku-calculation library, etc. | Zero on the caller side |
| AI integration | Write your own tool/function definitions | One OpenAPI URL |
| Initial cost | Looks like zero — but a single wrong-date liability sits behind it | Free tier, 10,000/month |

"Free because I built it myself" is only superficially free. **Priced with the business risk of a failure, it usually doesn't pay off** in this domain.

`fetch`

.Sample code is also collected under the `examples`

in [https://shirabe.dev/openapi.yaml](https://shirabe.dev/openapi.yaml). The same URL works whether you're calling from ChatGPT GPTs Actions, Claude tool use, or Gemini function calling.
