Enhanced the Time class to correctly calculate calendar
dates for the year range -999999 to +999999.
Made unit tests in C, C#, and Kotlin all exercise
the full year range, for February 28 and March 1 in each year,
to make sure we cover before and after each potential leap day.
The C functions for calculating calendar dates used the
type `long` to perform calculations that require 64-bit
integers. However, in some C compilers, `long` is still
32 bits. This caused a failure in Windows for extreme
year values. So I now use the type `int64_t` to explicitly
require a 64-bit integer.
In many of my Windows batch files, I used the following
construct to detect failures:
do_something
if errorlevel 1 (
echo.An error occurred in do_something
exit /b 1
)
I discovered that it is possible for a Windows program
to exit with a negative integer error code.
This causes the above construct to miss the failure
and the batch file blithely continues.
So I have replaced that construct with
do_something || (
echo.An error occurred in do_something
exit /b 1
)
This way, if the command exits with any nonzero error,
we correctly detect it as a failure.
Applying the same recent fixes to C and C# to the Python code.
I'm also changing my philosophy of representing times.
From now on, they will be truncated to the floor millisecond,
not rounded to the nearest millisecond. This means we don't reach
another calendar date until we have had 60 full seconds after
the last minute. Otherwise there is too much nasty logic for
rounding up calendar dates. I will follow suit across all languages.
Fixed problems converting AstroTime to calendar dates and back.
Also expose struct CalendarDateTime to outside callers,
for convenience dealing with Gregorian calendar dates.
With more rigorous testing, I discovered more bugs
in the C functions for converting calendar dates
to times and vice versa.
Astronomy_UtcFromTime():
When the year went before -4714, the value of the variable
`djd` went negative, causing the typecast `(long)djd` to
round toward zero instead of taking the true floor.
Changed this to `(long)floor(djd)`.
Astronomy_MakeTime():
Reworked the logic so that none of the integer divisions
involve negative values over the year range -999999..+999999.
Addressed limitations of the logic I copied from NOVAS cal_date().
Its formulas did not work for years much before -12000 due to
integer division going negative. I figured out how to make the
formulas work for plus or minus 1 million years from the present era.
Implemented and added tests for the following methods:
AstroTime.TryParse()
AstroVector.TryParse()
I still need to implement StateVector.TryParse().
Fixed a bug in CalendarDateTime: the NOVAS cal_date
function worked well, except when years go below
somewhere near -12000. Then the formulas start making
negative numbers, which messes up the calculation
of the month and day.
So to fix this, I figured out that any multiple of
400 years added to any calendar date gives the exact
same calendar date. And 400 years always has the exact
same number of days in it: 146097.
Because one million years = 400 * 2500 years, I can
add 2500*146097 days at the front of the formula
and subtract 1000000 years at the back, and everything
works for the entire range of years plus or minus
one million years from the present.
Because my date format allows for no more than a 6-digit
decimal year, this is perfectly adequate.
Replace the abstract class with a parameter of function type.
This allows the documentation to fully explain how to use
`CorrectLightTravel` without having to look at the code.
The generated code for the Pluto state table in Python
now uses a class `_pstate` for better type checking.
It also makes the code easier to understand.
Moved class _TerseVector higher in the source file to
reduce the need for quoted forward type declarations.
I added the mypy option `--disallow-untyped-defs` to fail
any function lacking complete type hints.
Then I fixed all the resulting errors.
I ended up changing the Python code generator to create
some tuple types instead of list, because it is possible
to write stricter type checks that way. This was in
the Pluto and Jupiter Moon tables.
I still should come back and do the same thing for the VSOP tables.
The type checking revealed a couple of places where I wasn't
checking for a search failure. I fixed those too.
My custom Markdown generator for Python documentation `pydown`
was generating inconsistent function type signatures depending
on the version of Python executing it. This happened for functions
like `Search` that return either Time or None.
On older Pythons we see "Optional[astronomy.Time]".
On newer Pythons we see "Union[astronomy.Time, NoneType]".
This caused unit test failures on GitHub Actions when I check in changes.
I prefer to see Optional[x] over Union[x, NoneType], so I hacked
pydown to replace this using a regex substitution.