Astro Binding Reports Wrong Moon Phase

Hi Guys,

Last month I began using the Astro Binding for the first time (I’m a relatively new openHAB user). All worked pretty well with one exception - the moon phase it was reporting was incorrect. It was just a couple of days before the full moon, yet the binding was reporting Waning Gibbous rather than Waxing Gibbous. I figured I must have had something mis-configured with the binding so ignored it at the time. Come the day of the full moon, the phase was correctly reported, as were subsequent phases.

However, a month later I’m noticing the same behaviour. I’m located on the eastern side of Australia, where the time is currently Wed 11 Dec just before 10pm. The binding is correctly reporting the Full Moon time as Thu 12 Dec 4:13 pm (matches an independent app on my phone). I.e. the full moon is roughly 18 hours from now. So the correct moon phase is definitely Waxing Gibbous yet the binding is reporting Waning Gibbous as seen in my Sitemap:

Any suggestions of what I’m doing wrong are appreciated. I’m currently running openHAB 2.5.0.M6.



The binding generates a string like WANING_GIBBOUS, not Waning gibbous. Is there a MAP you are hiding?

Interesting old post

Thanks rossko57 for your reply.

I haven’t added any mappings. The following are the relevant lines from my items and sitemap files respectively:

String moon_phase “Moon Phase” {channel=“astro:moon:local:phase#name”}

Default item=moon_phase

I did see that other thread before posting. I didn’t fully understand the solution - there was reference to an eclipse, but I’m not sure an eclipse actually changes the moon phase does it? Or was it just a glitch, similar to what I am seeing?


As I read the other post, it seemed to be a glitch with a finger of suspicion pointed at a possible simultaneous eclipse.

The state of your moon_phase Item should be a string like WANING_GIBBOUS.
You’re seeing a display Waning gibbous
There is mapping at work, even if hidden, which could be broken.

You can check the Item state using REST API which is useful tool if you haven’t used before.
That should also show us if the binding has added “default state descriptions” to your Item which might be where our mystery mapping comes from.
Can you do that, to rule this out?

Thanks rossko57.

I’m a complete novice with the Rest API so please forgive me if I have misunderstood where you want me to check. I installed the Rest API Documentation and then launched the Rest API from the dashboard. Under Items I selected Show, then Get All Available Items, left all the settings at their defaults and clicked Try it out! Below is the section that relates to my moon_phase item, which appears to show the mappings you refer to, but I can’t spot any errors in said mappings:

    "link": "http://localhost:8080/rest/items/moon_phase",
    "state": "WANING_GIBBOUS",
    "stateDescription": {
      "pattern": "%s",
      "readOnly": true,
      "options": [
          "value": "NEW",
          "label": "New moon"
          "value": "WAXING_CRESCENT",
          "label": "Waxing crescent"
          "value": "FIRST_QUARTER",
          "label": "First quarter"
          "value": "WAXING_GIBBOUS",
          "label": "Waxing gibbous"
          "value": "FULL",
          "label": "Full moon"
          "value": "WANING_GIBBOUS",
          "label": "Waning gibbous"
          "value": "THIRD_QUARTER",
          "label": "Third quarter"
          "value": "WANING_CRESCENT",
          "label": "Waning crescent"
    "commandDescription": {
      "commandOptions": [
          "command": "NEW",
          "label": "New moon"
          "command": "WAXING_CRESCENT",
          "label": "Waxing crescent"
          "command": "FIRST_QUARTER",
          "label": "First quarter"
          "command": "WAXING_GIBBOUS",
          "label": "Waxing gibbous"
          "command": "FULL",
          "label": "Full moon"
          "command": "WANING_GIBBOUS",
          "label": "Waning gibbous"
          "command": "THIRD_QUARTER",
          "label": "Third quarter"
          "command": "WANING_CRESCENT",
          "label": "Waning crescent"
    "editable": false,
    "type": "String",
    "name": "moon_phase",
    "label": "Moon Phase",
    "category": "moon",
    "tags": [],
    "groupNames": []

Is this the info you were requesting?



Yep, exactly that.

At least we know where the mystery text came from, and can see the error does not come from there.
Many people will be running with their own MAP (language etc.) so if there were an issue with default text, it would be mostly hidden, so worth confirming.

I think you have a bug there - what we need is a user of latest release to confirm it’s still there today. EDIT - forget that, see you are 2.5 M6
You should open a github issue for this, noting date of problem.

1 Like

Thanks rossko57, no probs re doing the check.

I’m currently running 2.5.0.M6. When I saw the same behaviour just prior to the last full moon I was running 2.4.0 stable from memory.



Can confirm I get WANING_GIBBOUS right now in northern hemisphere, with 2.4
So I raised an issue

Quickly browsing the astro binding code it appears that the moon phase is computed once: at midnight of the current Julian date (see the job folder and My guess is that it is computed at start of day.

There could be a problem on days where more than 1 phase occurs (e.g., WAXING_GIBBOUSFULLWANING_GIBBOUS).

I think the simplest workaround is to check the moon age (value between 0 and 1) instead of the moon phase, as the fomer is instantly computed by the astro binding.

Creating a rule to compute the correct instant phase name based on the instant lunar age (by using astro:moon:home:phase#agePercent) is rather straightforward.

Here’ a workaround with a SCALE transform:

Astro items:

Number:Dimensionless	Astro_Moon_Age_Percent	"Moon Age [%.2f %%]"				<moon>	{ channel="astro:moon:home:phase#agePercent" }

EDIT: The age is correctly translated to moon phase but the age is wrong



Note that for practical use I decided to show the phases (full/half/new) for 0.1% before/after a lunar cycle (roughly 29,5 days), or 48.8 minutes before/after the actual phase, as the mathematical phase is reached at a precise point in time (no duration). Otherwise you would (almost) never see the full/half phases reported. In case you want to reduce this time interval to 30 minutes before/after the actual phase, then you should use 0.0706% instead of 0.1% in the SCALE transform.


Default item=Astro_Moon_Age_Percent
Default item=Astro_Moon_Age_Percent label="Instant Moon Phase [SCALE(moon_phase.scale):%f]"

I’ve responded to this in the github issue.
I do see your point, and all might be seen on the 12th - but not Waning today on the 11th

The suggested workaround also gives me Waning right now, because the #agePercent appears to be incorrect today at 50.46%

I’m not sure what the “rules” of this game are - is it convention that any calendar day including the instant of Full moon would get “labelled” Full throughout the calendar day? Or does it span two dates but only between sunset and sunrise?

To my knowledge, the day on a calendar is marked as “Full / Half / New” if the exact time of the phase fits within the day (between 00:00:00.000 and 33:59:59.999).

I expect the binding to perform the following logic:

  • If the current day will host a lunar phase equal to NEW, FIRST_QUARTER, FULL or THIRD_QUARTER, then mark the current day with this exact lunar phase.
  • Else: mark the current day with the phase at midnight (no other phase expected).

I agree that there’s a mistake in the calculation. My take is that the “midnight” used by the astro binding differs from your locale’s midnight.

For the record, the instant phase can apparently easily be computed in Python as follows:

    ### See
    dt_now =
    k = 12.3685 * ( dt_now.getMillis() / 31557600000.0 - 30.0144003346 ) % 1
    LogAction.logInfo("Instant Lunar Age", u"Current instant lunar age is {} %".format("%.2f" % (k * 100)))

The accuracy of this formula is apparently sufficient. For the details I’ll have to look up the equation in my copy of Jean Meeus’ Astronomical Formulae for Calculators.

I’m on GMT, so that would be surprise.

There’s more than one definition of year or day to astronomers. My guess is that it’s not a 86.400 second day that is currently being used…

Here’s what I found so far in browsing the code (posted also in the Github thread):

I think there might be 2 problems in, more precisely in:
setMoonPhase(Calendar calendar, Moon moon):
1 phase.setAge( (int) age) might have adverse side effects as I don’t get the point in rounding age to a number of whole days.
2. It also appears that the waxing / waning definition is defined by the half length of a lunar cycle expressed in 86.400 second days: boolean isWaxing = age < (29.530588853 / 2); This number (29.530588853) represents the length of a synodic month, i.e., the time between 2 same lunar phases. The problem is that the Earth’s and the Moon’s orbit are elliptical, hence the actual synodic month varies over time. From Wikipedia:

Since Earth’s orbit around the Sun is elliptical and not circular, the speed of Earth’s progression around the Sun varies during the year. Thus, the angular rate is faster nearer periapsis and slower near apoapsis. The same is so for the Moon’s orbit around the Earth. Because of these variations in angular rate, the actual time between lunations may vary from about 29.18 to about 29.93 days. The long-term average duration is 29.530587981 days[5] (29 d 12 h 44 min 2.8016 s). The synodic month is used to calculate eclipse cycles.[6]

I think you might be saying Waxing/Waning is determined by a different mechanism to Full, with a different uhh centre scale point? That would explain Waning before Full.
The bit of info I’m missing is if Waxing Gibbous ever occurs - did you notice @Dave2 ?

Just now (on GMT) I have

2019-12-12 22:13:04.699 [vent.ItemStateChangedEvent] - moon_age changed from NULL to 55.075640113909294 %
2019-12-12 22:13:04.700 [vent.ItemStateChangedEvent] - moon_phase changed from NULL to FULL

i.e. “last night” was I believe a FULL moon, actually early hours of “this morning”, but just now it is still 12th calendar date. So that seems all good.

I expect that to rollover at midnight, will confirm.
EDIT - yep

2019-12-13 00:00:30.089 [vent.ItemStateChangedEvent] - moon_age changed from 55.31034399668286 % to 55.32776519507749 %
2019-12-13 00:00:30.124 [vent.ItemStateChangedEvent] - moon_phase changed from FULL to WANING_GIBBOUS

So moon_phase is actually not the moon phase but is sort of inverse of “day in which an exact phase occurs, or if not, the phase inbetween”. The naming is hence a bit confusing.

@rossko57 yes, searching back through the event log files on my system waxing gibbous was first reported (for this lunar cycle) on 5 December (i.e. the correct date - after the first quarter):

2019-12-05 00:00:27.965 [vent.ItemStateChangedEvent] - moon_phase changed from FIRST_QUARTER to WAXING_GIBBOUS

However on 11 December it changed to waning:

2019-12-11 00:00:30.522 [vent.ItemStateChangedEvent] - moon_phase changed from WAXING_GIBBOUS to WANING_GIBBOUS



1 Like

The way I interpret it, the phase name text from Astro is analogous to symbols on a printed diary or calendar. One calendar day is marked “Full” - with a presumption / limitation that it is “Full” all day (because that’s how a calendar works).
And then, yep, it fills in the days between the quarters. That bit is going wrong, sometimes.

That seems reasonable to me - folk will want openHAB to agree with their desk diary. Anyone wanting a more astronomical approach has the numeric age available. Or should have … looks like you’ve uncovered an error there too.

Okay, it looks like the waxing>waning change is some hours out. Maybe this shows up when the actual moment of Full is close to midnight, local time or UTC - but is otherwise hidden. Probably all down to the inaccurate age.

I’ll bet it affects third quarter too but not as obviously.

While looking for accurate algorithms I stumbled upon this tread. I gave skyfield a try. Installing on openHABian was straightforward:

$ sudo pip install skyfield
Looking in indexes:,
Collecting skyfield
  Downloading (226kB)
    100% |████████████████████████████████| 235kB 918kB/s 
Collecting jplephem>=2.3 (from skyfield)
Requirement already satisfied: numpy in /usr/local/lib/python2.7/dist-packages (from skyfield) (1.16.5)
Collecting sgp4>=1.4 (from skyfield)
Building wheels for collected packages: skyfield, jplephem, sgp4
  Running bdist_wheel for skyfield ... done
  Stored in directory: /root/.cache/pip/wheels/e5/b5/b2/4aaf2c25bb51376bd0ebeb7671f2223c2732101b77c500eaa2
  Running bdist_wheel for jplephem ... done
  Stored in directory: /root/.cache/pip/wheels/c1/6f/9e/392c7e798a41b202e6008e8cf711a04c9aafebb7fc684c1d94
  Running bdist_wheel for sgp4 ... done
  Stored in directory: /root/.cache/pip/wheels/6d/e2/42/5dc20daf2ba62ae03dc8abe10744ee67d9452df447dac561db
Successfully built skyfield jplephem sgp4
Installing collected packages: jplephem, sgp4, skyfield
Successfully installed jplephem-2.11 sgp4-1.4 skyfield-1.15

And running from the interactive shell:

$ python
Python 2.7.16 (default, Oct 10 2019, 22:02:15) 
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from skyfield import api
>>> ts = api.load.timescale()
[#################################] 100%
[#################################] 100% deltat.preds
[#################################] 100% Leap_Second.dat
>>> e = api.load('de421.bsp')
[#################################] 100% de421.bsp
>>> from skyfield import almanac
>>> t0 = ts.utc(2019,1,1)
>>> t1 = ts.utc(2019,12,31)
>>> t, y = almanac.find_discrete(t0, t1, almanac.moon_phases(e))
>>> print(t.utc_iso())
['2019-01-06T01:28:11Z', '2019-01-14T06:45:31Z', '2019-01-21T05:16:05Z', '2019-01-27T21:10:20Z', '2019-02-04T21:03:35Z', '2019-02-12T22:26:14Z', '2019-02-19T15:53:35Z', '2019-02-26T11:27:43Z', '2019-03-06T16:03:58Z', '2019-03-14T10:27:05Z', '2019-03-21T01:42:52Z', '2019-03-28T04:09:41Z', '2019-04-05T08:50:29Z', '2019-04-12T19:05:53Z', '2019-04-19T11:12:10Z', '2019-04-26T22:18:18Z', '2019-05-04T22:45:30Z', '2019-05-12T01:12:14Z', '2019-05-18T21:11:21Z', '2019-05-26T16:33:35Z', '2019-06-03T10:01:57Z', '2019-06-10T05:59:18Z', '2019-06-17T08:30:40Z', '2019-06-25T09:46:24Z', '2019-07-02T19:16:13Z', '2019-07-09T10:54:51Z', '2019-07-16T21:38:13Z', '2019-07-25T01:18:01Z', '2019-08-01T03:11:55Z', '2019-08-07T17:30:57Z', '2019-08-15T12:29:15Z', '2019-08-23T14:56:06Z', '2019-08-30T10:37:09Z', '2019-09-06T03:10:26Z', '2019-09-14T04:32:46Z', '2019-09-22T02:40:54Z', '2019-09-28T18:26:22Z', '2019-10-05T16:47:06Z', '2019-10-13T21:07:52Z', '2019-10-21T12:39:18Z', '2019-10-28T03:38:28Z', '2019-11-04T10:23:05Z', '2019-11-12T13:34:24Z', '2019-11-19T21:10:56Z', '2019-11-26T15:05:35Z', '2019-12-04T06:58:13Z', '2019-12-12T05:12:16Z', '2019-12-19T04:57:05Z', '2019-12-26T05:13:08Z']
>>> print(y)
[0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0
 1 2 3 0 1 2 3 0 1 2 3 0]
>>> print([almanac.MOON_PHASES[yi] for yi in y])
['New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon', 'First Quarter', 'Full Moon', 'Last Quarter', 'New Moon']

So the accurate timestamp of yesterday’s Full Moon is 2019-12-12T05:12:16Z (Dec 12, 2019 @ 05:12:16 AM UTC).

As you see the almanac returns results in UTC. These must be converted back to local time, which may shift the lunar phase by 1 day from time to time.

And here’s a way to quickly display the almanac in UTC and local time (make sure to define the interval as the full year + 31/12 + 01/01 to ensure you capture all lunar phase events in a calendar year in local time:

>>> t0 = ts.utc(2018, 12, 31)
>>> t1 = ts.utc(2020,1, 1)
>>> t, y = almanac.find_discrete(t0, t1, almanac.moon_phases(e))
>>> for ts_utc, phase in zip(t, y):
...   print "{}\t{} UTC\t{} local time".format(("%-16s" % almanac.MOON_PHASES[phase]), ts_utc.astimezone(from_zone), ts_utc.astimezone(to_zone))
New Moon        	2019-01-06 01:28:11.221000+00:00 UTC	2019-01-06 02:28:11.221000+01:00 local time
First Quarter   	2019-01-14 06:45:31.132000+00:00 UTC	2019-01-14 07:45:31.132000+01:00 local time
Full Moon       	2019-01-21 05:16:04.684000+00:00 UTC	2019-01-21 06:16:04.684000+01:00 local time
Last Quarter    	2019-01-27 21:10:20.257000+00:00 UTC	2019-01-27 22:10:20.257000+01:00 local time
New Moon        	2019-02-04 21:03:34.875000+00:00 UTC	2019-02-04 22:03:34.875000+01:00 local time
First Quarter   	2019-02-12 22:26:13.948000+00:00 UTC	2019-02-12 23:26:13.948000+01:00 local time
Full Moon       	2019-02-19 15:53:34.539000+00:00 UTC	2019-02-19 16:53:34.539000+01:00 local time
Last Quarter    	2019-02-26 11:27:42.842000+00:00 UTC	2019-02-26 12:27:42.842000+01:00 local time
New Moon        	2019-03-06 16:03:58.269000+00:00 UTC	2019-03-06 17:03:58.269000+01:00 local time
First Quarter   	2019-03-14 10:27:05.233000+00:00 UTC	2019-03-14 11:27:05.233000+01:00 local time
Full Moon       	2019-03-21 01:42:51.567000+00:00 UTC	2019-03-21 02:42:51.567000+01:00 local time
Last Quarter    	2019-03-28 04:09:41.192000+00:00 UTC	2019-03-28 05:09:41.192000+01:00 local time
New Moon        	2019-04-05 08:50:28.624000+00:00 UTC	2019-04-05 10:50:28.624000+02:00 local time
First Quarter   	2019-04-12 19:05:52.970000+00:00 UTC	2019-04-12 21:05:52.970000+02:00 local time
Full Moon       	2019-04-19 11:12:10.205000+00:00 UTC	2019-04-19 13:12:10.205000+02:00 local time
Last Quarter    	2019-04-26 22:18:17.812000+00:00 UTC	2019-04-27 00:18:17.812000+02:00 local time
New Moon        	2019-05-04 22:45:29.812000+00:00 UTC	2019-05-05 00:45:29.812000+02:00 local time
First Quarter   	2019-05-12 01:12:14.118000+00:00 UTC	2019-05-12 03:12:14.118000+02:00 local time
Full Moon       	2019-05-18 21:11:21.081000+00:00 UTC	2019-05-18 23:11:21.081000+02:00 local time
Last Quarter    	2019-05-26 16:33:34.968000+00:00 UTC	2019-05-26 18:33:34.968000+02:00 local time
New Moon        	2019-06-03 10:01:56.654000+00:00 UTC	2019-06-03 12:01:56.654000+02:00 local time
First Quarter   	2019-06-10 05:59:17.999000+00:00 UTC	2019-06-10 07:59:17.999000+02:00 local time
Full Moon       	2019-06-17 08:30:39.681000+00:00 UTC	2019-06-17 10:30:39.681000+02:00 local time
Last Quarter    	2019-06-25 09:46:23.763000+00:00 UTC	2019-06-25 11:46:23.763000+02:00 local time
New Moon        	2019-07-02 19:16:12.664000+00:00 UTC	2019-07-02 21:16:12.664000+02:00 local time
First Quarter   	2019-07-09 10:54:51.155000+00:00 UTC	2019-07-09 12:54:51.155000+02:00 local time
Full Moon       	2019-07-16 21:38:13.077000+00:00 UTC	2019-07-16 23:38:13.077000+02:00 local time
Last Quarter    	2019-07-25 01:18:01.468000+00:00 UTC	2019-07-25 03:18:01.468000+02:00 local time
New Moon        	2019-08-01 03:11:55.249000+00:00 UTC	2019-08-01 05:11:55.249000+02:00 local time
First Quarter   	2019-08-07 17:30:57.356000+00:00 UTC	2019-08-07 19:30:57.356000+02:00 local time
Full Moon       	2019-08-15 12:29:15.253000+00:00 UTC	2019-08-15 14:29:15.253000+02:00 local time
Last Quarter    	2019-08-23 14:56:06.001000+00:00 UTC	2019-08-23 16:56:06.001000+02:00 local time
New Moon        	2019-08-30 10:37:08.502000+00:00 UTC	2019-08-30 12:37:08.502000+02:00 local time
First Quarter   	2019-09-06 03:10:25.639000+00:00 UTC	2019-09-06 05:10:25.639000+02:00 local time
Full Moon       	2019-09-14 04:32:45.902000+00:00 UTC	2019-09-14 06:32:45.902000+02:00 local time
Last Quarter    	2019-09-22 02:40:54.116000+00:00 UTC	2019-09-22 04:40:54.116000+02:00 local time
New Moon        	2019-09-28 18:26:21.566000+00:00 UTC	2019-09-28 20:26:21.566000+02:00 local time
First Quarter   	2019-10-05 16:47:06.199000+00:00 UTC	2019-10-05 18:47:06.199000+02:00 local time
Full Moon       	2019-10-13 21:07:52.232000+00:00 UTC	2019-10-13 23:07:52.232000+02:00 local time
Last Quarter    	2019-10-21 12:39:18.100000+00:00 UTC	2019-10-21 14:39:18.100000+02:00 local time
New Moon        	2019-10-28 03:38:27.754000+00:00 UTC	2019-10-28 04:38:27.754000+01:00 local time
First Quarter   	2019-11-04 10:23:04.635000+00:00 UTC	2019-11-04 11:23:04.635000+01:00 local time
Full Moon       	2019-11-12 13:34:24.114000+00:00 UTC	2019-11-12 14:34:24.114000+01:00 local time
Last Quarter    	2019-11-19 21:10:56.019000+00:00 UTC	2019-11-19 22:10:56.019000+01:00 local time
New Moon        	2019-11-26 15:05:35.280000+00:00 UTC	2019-11-26 16:05:35.280000+01:00 local time
First Quarter   	2019-12-04 06:58:13.304000+00:00 UTC	2019-12-04 07:58:13.304000+01:00 local time
Full Moon       	2019-12-12 05:12:15.923000+00:00 UTC	2019-12-12 06:12:15.923000+01:00 local time
Last Quarter    	2019-12-19 04:57:04.968000+00:00 UTC	2019-12-19 05:57:04.968000+01:00 local time
New Moon        	2019-12-26 05:13:07.751000+00:00 UTC	2019-12-26 06:13:07.751000+01:00 local time

It appears that we reach millisecond accuracy with skyfield.

And you’ll also see that sometimes the UTC date for a given precise lunar phase differs from the local time date, as is the case with:

Last Quarter    	2019-04-26 22:18:17.812000+00:00 UTC	2019-04-27 00:18:17.812000+02:00 local time
New Moon        	2019-05-04 22:45:29.812000+00:00 UTC	2019-05-05 00:45:29.812000+02:00 local time

An option is to replace the current astro binding with a JSR223 Python implemented rule that uses skyfield. To do so, ask pip to install skyfield (and other dependencies) in /etc/openhab2/automation/lib/python/ so the rules can use skyfield:

$ pip install --target=/etc/openhab2/automation/lib/python skyfield

A starting point is e.g. Almanac Computation