diff --git a/generate/rungo b/generate/rungo
new file mode 100755
index 00000000..c71154ef
--- /dev/null
+++ b/generate/rungo
@@ -0,0 +1,4 @@
+#!/bin/bash
+./generate source || exit 1
+./unit_test_golang || exit 1
+exit 0
diff --git a/generate/template/astronomy.go b/generate/template/astronomy.go
index 2ab6f104..41f3e314 100644
--- a/generate/template/astronomy.go
+++ b/generate/template/astronomy.go
@@ -7,7 +7,10 @@
// [Astronomy Engine]: https://github.com/cosinekitty/astronomy
package astronomy
-import "math"
+import (
+ "errors"
+ "math"
+)
const (
DaysPerTropicalYear = 365.24217 // the number of days in one tropical year
@@ -244,13 +247,66 @@ func (time AstroTime) AddDays(days float64) AstroTime {
return TimeFromUniversalDays(time.Ut + days)
}
+// CalendarDateTime represents a Gregorian calendar date and time within
+// plus or minus 1 million years from the year 0.
type CalendarDateTime struct {
- Year int
- Month int
- Day int
- Hour int
- Minute int
- Second float64
+ Year int // The year value in the range -999999 to +999999.
+ Month int // The calendar month in the range 1..12.
+ Day int // The day of the month in the range 1..31.
+ Hour int // The hour in the range 0..23.
+ Minute int // The minute in the range 0..59.
+ Second float64 // The real-valued second in the half-open range [0, 60).
+}
+
+// CalendarFromDays converts a J2000 day value to a Gregorian calendar date and time.
+func CalendarFromDays(ut float64) (*CalendarDateTime, error) {
+ // Adapted from the NOVAS C 3.1 function cal_date().
+ // Convert fractional days since J2000 into Gregorian calendar date/time.
+ djd := ut + 2451545.5
+ jd := int64(math.Floor(djd))
+ var x float64
+ x = 24.0 * math.Mod(djd, 1.0)
+ if x < 0.0 {
+ x += 24.0
+ }
+ hour := int(x)
+ x = 60.0 * math.Mod(x, 1.0)
+ minute := int(x)
+ second := 60.0 * math.Mod(x, 1.0)
+
+ // This is my own adjustment to the NOVAS cal_date logic
+ // so that it can handle dates much farther back in the past.
+ // I add c*400 years worth of days at the front,
+ // then subtract c*400 years at the back,
+ // which avoids negative values in the formulas that mess up
+ // the calendar date calculations.
+ // Any multiple of 400 years has the same number of days,
+ // because it eliminates all the special cases for leap years.
+ const c = 2500
+
+ var k, n, m int64
+ k = jd + (68569 + c*146097)
+ n = (4 * k) / 146097
+ k = k - (146097*n+3)/4
+ m = (4000 * (k + 1)) / 1461001
+ k = k - (1461*m)/4 + 31
+
+ month := (int)((80 * k) / 2447)
+ day := (int)(k - int64(2447*month)/80)
+ k = int64(month / 11)
+
+ month = int(int64(month+2) - 12*k)
+ year := int(100*(n-49) + m + k - 400*c)
+
+ if year < -999999 || year > +999999 {
+ return nil, errors.New("The supplied time is too far from the year 2000 to be represented.")
+ }
+
+ if month < 1 || month > 12 || day < 1 || day > 31 {
+ return nil, errors.New("Internal error: invalid calendar date calculated.")
+ }
+
+ return &CalendarDateTime{year, month, day, hour, minute, second}, nil
}
type AstroVector struct {
diff --git a/generate/unit_test_golang b/generate/unit_test_golang
index 285f094c..eada9cc7 100755
--- a/generate/unit_test_golang
+++ b/generate/unit_test_golang
@@ -10,6 +10,7 @@ Fail()
go version || Fail "Cannot find Go compiler."
cd ../source/golang
+go fmt ./... || Fail "Error formatting Go code."
go test -v || Fail "Failure in Go unit tests."
cd ../../generate
echo "unit_test_golang: success"
diff --git a/source/golang/README.md b/source/golang/README.md
index e37f7449..634e9303 100644
--- a/source/golang/README.md
+++ b/source/golang/README.md
@@ -28,6 +28,7 @@ It provides a suite of well\-tested functions for calculating positions of the S
- [type AxisInfo](<#AxisInfo>)
- [type Body](<#Body>)
- [type CalendarDateTime](<#CalendarDateTime>)
+ - [func CalendarFromDays\(ut float64\) \(\*CalendarDateTime, error\)](<#CalendarFromDays>)
- [type DeltaTimeFunc](<#DeltaTimeFunc>)
- [type Ecliptic](<#Ecliptic>)
- [type Equatorial](<#Equatorial>)
@@ -125,7 +126,7 @@ const (
```
-## func [AngleBetween]()
+## func [AngleBetween]()
```go
func AngleBetween(avec AstroVector, bvec AstroVector) float64
@@ -134,7 +135,7 @@ func AngleBetween(avec AstroVector, bvec AstroVector) float64
AngleBetween calculates the angle in degrees between two vectors. Given a pair of vectors avec and bvec, this function returns the angle in degrees between the vectors in 3D space. The angle is measured in the plane that contains both vectors. The returned value is in the closed range \[0, 180\].
-## func [DegreesFromRadians]()
+## func [DegreesFromRadians]()
```go
func DegreesFromRadians(radians float64) float64
@@ -143,7 +144,7 @@ func DegreesFromRadians(radians float64) float64
DegreesFromRadians converts an angle expressed in radians to an angle expressed in degrees.
-## func [RadiansFromDegrees]()
+## func [RadiansFromDegrees]()
```go
func RadiansFromDegrees(degrees float64) float64
@@ -152,7 +153,7 @@ func RadiansFromDegrees(degrees float64) float64
RadiansFromDegrees converts an angle expressed in degrees to an angle expressed in radians.
-## type [AstroMoonQuarter]()
+## type [AstroMoonQuarter]()
@@ -164,7 +165,7 @@ type AstroMoonQuarter struct {
```
-## type [AstroSearchFunc]()
+## type [AstroSearchFunc]()
@@ -173,7 +174,7 @@ type AstroSearchFunc func(context interface{}, time AstroTime) float64
```
-## type [AstroTime]()
+## type [AstroTime]()
@@ -221,7 +222,7 @@ type AstroTime struct {
```
-### func [TimeFromTerrestrialDays]()
+### func [TimeFromTerrestrialDays]()
```go
func TimeFromTerrestrialDays(tt float64) AstroTime
@@ -230,7 +231,7 @@ func TimeFromTerrestrialDays(tt float64) AstroTime
-### func [TimeFromUniversalDays]()
+### func [TimeFromUniversalDays]()
```go
func TimeFromUniversalDays(ut float64) AstroTime
@@ -239,7 +240,7 @@ func TimeFromUniversalDays(ut float64) AstroTime
-### func \(AstroTime\) [AddDays]()
+### func \(AstroTime\) [AddDays]()
```go
func (time AstroTime) AddDays(days float64) AstroTime
@@ -248,7 +249,7 @@ func (time AstroTime) AddDays(days float64) AstroTime
-## type [AstroVector]()
+## type [AstroVector]()
@@ -262,7 +263,7 @@ type AstroVector struct {
```
-### func \(AstroVector\) [Length]()
+### func \(AstroVector\) [Length]()
```go
func (vec AstroVector) Length() float64
@@ -271,7 +272,7 @@ func (vec AstroVector) Length() float64
Returns the length of vec expressed in the same distance units as vec's components.
-## type [AtmosphereInfo]()
+## type [AtmosphereInfo]()
@@ -284,7 +285,7 @@ type AtmosphereInfo struct {
```
-## type [AxisInfo]()
+## type [AxisInfo]()
@@ -298,7 +299,7 @@ type AxisInfo struct {
```
-## type [Body]()
+## type [Body]()
@@ -307,23 +308,32 @@ type Body int
```
-## type [CalendarDateTime]()
-
+## type [CalendarDateTime]()
+CalendarDateTime represents a Gregorian calendar date and time within plus or minus 1 million years from the year 0.
```go
type CalendarDateTime struct {
- Year int
- Month int
- Day int
- Hour int
- Minute int
- Second float64
+ Year int // The year value in the range -999999 to +999999.
+ Month int // The calendar month in the range 1..12.
+ Day int // The day of the month in the range 1..31.
+ Hour int // The hour in the range 0..23.
+ Minute int // The minute in the range 0..59.
+ Second float64 // The real-valued second in the half-open range [0, 60).
}
```
+
+### func [CalendarFromDays]()
+
+```go
+func CalendarFromDays(ut float64) (*CalendarDateTime, error)
+```
+
+CalendarFromDays converts a J2000 day value to a Gregorian calendar date and time.
+
-## type [DeltaTimeFunc]()
+## type [DeltaTimeFunc]()
@@ -332,7 +342,7 @@ type DeltaTimeFunc func(ut float64) float64
```
-## type [Ecliptic]()
+## type [Ecliptic]()
@@ -345,7 +355,7 @@ type Ecliptic struct {
```
-## type [Equatorial]()
+## type [Equatorial]()
@@ -359,7 +369,7 @@ type Equatorial struct {
```
-## type [JupiterMoonsInfo]()
+## type [JupiterMoonsInfo]()
@@ -373,7 +383,7 @@ type JupiterMoonsInfo struct {
```
-## type [LibrationInfo]()
+## type [LibrationInfo]()
@@ -389,7 +399,7 @@ type LibrationInfo struct {
```
-## type [NodeEventInfo]()
+## type [NodeEventInfo]()
@@ -401,7 +411,7 @@ type NodeEventInfo struct {
```
-## type [NodeEventKind]()
+## type [NodeEventKind]()
@@ -410,7 +420,7 @@ type NodeEventKind int
```
-## type [Observer]()
+## type [Observer]()
@@ -423,7 +433,7 @@ type Observer struct {
```
-## type [Refraction]()
+## type [Refraction]()
@@ -442,7 +452,7 @@ const (
```
-## type [RotationMatrix]()
+## type [RotationMatrix]()
@@ -453,7 +463,7 @@ type RotationMatrix struct {
```
-## type [SeasonsInfo]()
+## type [SeasonsInfo]()
@@ -467,7 +477,7 @@ type SeasonsInfo struct {
```
-## type [Spherical]()
+## type [Spherical]()
@@ -480,7 +490,7 @@ type Spherical struct {
```
-## type [StateVector]()
+## type [StateVector]()
@@ -497,7 +507,7 @@ type StateVector struct {
```
-### func \(StateVector\) [Position]()
+### func \(StateVector\) [Position]()
```go
func (state StateVector) Position() AstroVector
@@ -506,7 +516,7 @@ func (state StateVector) Position() AstroVector
Position returns the position vector inside a state vector.
-### func \(StateVector\) [Velocity]()
+### func \(StateVector\) [Velocity]()
```go
func (state StateVector) Velocity() AstroVector
@@ -515,7 +525,7 @@ func (state StateVector) Velocity() AstroVector
Position returns the velocity vector inside a state vector.
-## type [TimeFormat]()
+## type [TimeFormat]()
@@ -535,7 +545,7 @@ const (
```
-## type [Topocentric]()
+## type [Topocentric]()
diff --git a/source/golang/astronomy.go b/source/golang/astronomy.go
index 8ff3e299..60d00c12 100644
--- a/source/golang/astronomy.go
+++ b/source/golang/astronomy.go
@@ -7,7 +7,10 @@
// [Astronomy Engine]: https://github.com/cosinekitty/astronomy
package astronomy
-import "math"
+import (
+ "errors"
+ "math"
+)
const (
DaysPerTropicalYear = 365.24217 // the number of days in one tropical year
@@ -244,13 +247,66 @@ func (time AstroTime) AddDays(days float64) AstroTime {
return TimeFromUniversalDays(time.Ut + days)
}
+// CalendarDateTime represents a Gregorian calendar date and time within
+// plus or minus 1 million years from the year 0.
type CalendarDateTime struct {
- Year int
- Month int
- Day int
- Hour int
- Minute int
- Second float64
+ Year int // The year value in the range -999999 to +999999.
+ Month int // The calendar month in the range 1..12.
+ Day int // The day of the month in the range 1..31.
+ Hour int // The hour in the range 0..23.
+ Minute int // The minute in the range 0..59.
+ Second float64 // The real-valued second in the half-open range [0, 60).
+}
+
+// CalendarFromDays converts a J2000 day value to a Gregorian calendar date and time.
+func CalendarFromDays(ut float64) (*CalendarDateTime, error) {
+ // Adapted from the NOVAS C 3.1 function cal_date().
+ // Convert fractional days since J2000 into Gregorian calendar date/time.
+ djd := ut + 2451545.5
+ jd := int64(math.Floor(djd))
+ var x float64
+ x = 24.0 * math.Mod(djd, 1.0)
+ if x < 0.0 {
+ x += 24.0
+ }
+ hour := int(x)
+ x = 60.0 * math.Mod(x, 1.0)
+ minute := int(x)
+ second := 60.0 * math.Mod(x, 1.0)
+
+ // This is my own adjustment to the NOVAS cal_date logic
+ // so that it can handle dates much farther back in the past.
+ // I add c*400 years worth of days at the front,
+ // then subtract c*400 years at the back,
+ // which avoids negative values in the formulas that mess up
+ // the calendar date calculations.
+ // Any multiple of 400 years has the same number of days,
+ // because it eliminates all the special cases for leap years.
+ const c = 2500
+
+ var k, n, m int64
+ k = jd + (68569 + c*146097)
+ n = (4 * k) / 146097
+ k = k - (146097*n+3)/4
+ m = (4000 * (k + 1)) / 1461001
+ k = k - (1461*m)/4 + 31
+
+ month := (int)((80 * k) / 2447)
+ day := (int)(k - int64(2447*month)/80)
+ k = int64(month / 11)
+
+ month = int(int64(month+2) - 12*k)
+ year := int(100*(n-49) + m + k - 400*c)
+
+ if year < -999999 || year > +999999 {
+ return nil, errors.New("The supplied time is too far from the year 2000 to be represented.")
+ }
+
+ if month < 1 || month > 12 || day < 1 || day > 31 {
+ return nil, errors.New("Internal error: invalid calendar date calculated.")
+ }
+
+ return &CalendarDateTime{year, month, day, hour, minute, second}, nil
}
type AstroVector struct {
diff --git a/source/golang/astronomy_test.go b/source/golang/astronomy_test.go
index 498d0595..cb64e9b3 100644
--- a/source/golang/astronomy_test.go
+++ b/source/golang/astronomy_test.go
@@ -38,3 +38,31 @@ func TestAngleBetween(t *testing.T) {
t.Errorf("Excessive angle error = %g", diff)
}
}
+
+func TestCalendar(t *testing.T) {
+ ut := 8679.201044872223
+ cal, err := CalendarFromDays(ut)
+ if err != nil {
+ t.Error(err)
+ }
+ // (2023, 10, 6, 16, 49, 30.276975631713867)
+ if cal.Year != 2023 {
+ t.Errorf("Expected year = 2023 but found %d", cal.Year)
+ }
+ if cal.Month != 10 {
+ t.Errorf("Expected month = 10 but found %d", cal.Month)
+ }
+ if cal.Day != 6 {
+ t.Errorf("Expected day = 6 but found %d.", cal.Day)
+ }
+ if cal.Hour != 16 {
+ t.Errorf("Expected hour = 16 but found %d.", cal.Hour)
+ }
+ if cal.Minute != 49 {
+ t.Errorf("Expected minute = 49 but found %d.", cal.Minute)
+ }
+ diff := math.Abs(cal.Second - 30.276975631713867)
+ if diff > 1.0e-16 {
+ t.Errorf("Excessive error calculating calendar seconds: %0.16g", diff)
+ }
+}