diff --git a/_package/tests/unit_tests/time_conversion_pyt.py b/_package/tests/unit_tests/time_conversion_pyt.py index 585397f7..c518312c 100644 --- a/_package/tests/unit_tests/time_conversion_pyt.py +++ b/_package/tests/unit_tests/time_conversion_pyt.py @@ -17,13 +17,18 @@ class TimeConversionTests(unittest.TestCase): """Tests functions in time_conversion.py.""" def test_datetime_to_julian(self): - """Test conversions between Julian to calendar date and times.""" + """Test datetime_to_julian.""" date_time = datetime.datetime(year=2004, month=6, day=3, hour=2, minute=8, second=32) julian = datetime_to_julian(date_time) self.assertEqual(2453159.5892592594, julian) def test_julian_to_datetime(self): - """Test conversions between Julian to calendar date and times.""" + """Test julian_to_datetime.""" date_time = julian_to_datetime(2453159.5892592594) expected_date_time = datetime.datetime(year=2004, month=6, day=3, hour=2, minute=8, second=32) self.assertEqual(expected_date_time, date_time) + + def test_julian_to_datetime_invalid(self): + """Test julian_to_datetime with invalid julian input.""" + date_time = julian_to_datetime(-99999999.0) + self.assertIsNone(date_time) diff --git a/_package/xms/core/__init__.py b/_package/xms/core/__init__.py index 11a5c647..f90adbcb 100644 --- a/_package/xms/core/__init__.py +++ b/_package/xms/core/__init__.py @@ -5,4 +5,4 @@ from . import misc # NOQA: F401 from . import time # NOQA: F401 -__version__ = '6.1.1' +__version__ = '6.1.2' diff --git a/_package/xms/core/time/time_conversion.py b/_package/xms/core/time/time_conversion.py index 87e6af8a..09fb7190 100644 --- a/_package/xms/core/time/time_conversion.py +++ b/_package/xms/core/time/time_conversion.py @@ -26,13 +26,16 @@ def julian_to_datetime(julian: float) -> Optional[datetime]: Returns: datetime.datetime: A Python datetime representing julian, or None if date is invalid. """ - year, month, day, hour, minute, second = time_cpp.tmJulianToCalendar(julian) + result = time_cpp.tmJulianToCalendar(julian) + if result is None: + return None + year, month, day, hour, minute, second = result return datetime(year=year, month=month, day=day, hour=hour, minute=minute, second=second, microsecond=0) -def datetime_to_julian(dt: datetime) -> float: +def datetime_to_julian(dt: datetime) -> Optional[float]: """Convert a Python datetime to a Julian double. Args: diff --git a/xmscore/python/time/TimeConversion_py.cpp b/xmscore/python/time/TimeConversion_py.cpp index 8a0c5958..716f6651 100644 --- a/xmscore/python/time/TimeConversion_py.cpp +++ b/xmscore/python/time/TimeConversion_py.cpp @@ -20,19 +20,22 @@ void initTimeConversion(py::module& geometry) // function: tmCalendarToJulian // --------------------------------------------------------------------------- geometry.def("tmCalendarToJulian", - [](int a_yr, int a_mo, int a_day, int a_hr, int a_min, int a_sec) -> double { + [](int a_yr, int a_mo, int a_day, int a_hr, int a_min, int a_sec) -> py::object { double julian = 0.0; - xms::tmCalendarToJulian(xms::ERA_CE, a_yr, a_mo, a_day, a_hr, a_min, a_sec, - &julian); - return julian; + bool success = + xms::tmCalendarToJulian(a_yr, a_mo, a_day, a_hr, a_min, a_sec, &julian); + if (success) + return py::cast(julian); + return py::none(); }); // --------------------------------------------------------------------------- // function: tmJulianToCalendar // --------------------------------------------------------------------------- - geometry.def("tmJulianToCalendar", [](double a_julian) -> py::tuple { + geometry.def("tmJulianToCalendar", [](double a_julian) -> py::object { int year, month, day, hour, minute, second; - xms::TimeEra era; // ignored like in data_objects - xms::tmJulianToCalendar(&era, &year, &month, &day, &hour, &minute, &second, a_julian); - return py::make_tuple(year, month, day, hour, minute, second); + bool success = xms::tmJulianToCalendar(&year, &month, &day, &hour, &minute, &second, a_julian); + if (success) + return py::make_tuple(year, month, day, hour, minute, second); + return py::none(); }); } diff --git a/xmscore/time/TimeConversion.cpp b/xmscore/time/TimeConversion.cpp index 773c696c..3d464386 100644 --- a/xmscore/time/TimeConversion.cpp +++ b/xmscore/time/TimeConversion.cpp @@ -17,10 +17,12 @@ #include // 4. External library headers +#include // 5. Shared code headers // 6. Non-shared code headers +#include //----- Forward declarations --------------------------------------------------- @@ -37,6 +39,13 @@ namespace xms //----- Class / Function definitions ------------------------------------------- +namespace +{ +enum TimeEra { + ERA_BCE, ///< Time era BCE (BC) + ERA_CE ///< Time era CE (AD) +}; + //------------------------------------------------------------------------------ /// \brief Convert a calendar date and time to Julian date and time. /// @@ -53,69 +62,69 @@ namespace xms /// Washington, DC 20392-5420 /// \endverbatim /// -/// \param a_bEra The input era: ERA_BCE - BC, ERA_CE - AD. -/// \param a_yr The input year. -/// \param a_mo The input month. +/// \param a_era The input era: ERA_BCE - BC, ERA_CE - AD. +/// \param a_year The input year. +/// \param a_month The input month. /// \param a_day The input day. -/// \param a_hr The input hour. -/// \param a_min The input minute. -/// \param a_sec The input second. +/// \param a_hour The input hour. +/// \param a_minute The input minute. +/// \param a_second The input second. /// \param a_julian The output julian date and time. /// \return Returns 1 if successful and -1 if unsuccessful. //------------------------------------------------------------------------------ -int tmCalendarToJulian(TimeEra a_bEra, - int a_yr, - int a_mo, - int a_day, - int a_hr, - int a_min, - int a_sec, - double* a_julian) +int iCalendarToJulian(TimeEra a_era, + int a_year, + int a_month, + int a_day, + int a_hour, + int a_minute, + int a_second, + double* a_julian) { double jy, ja, jm, tmpyr; double intgr, gregcal, dayfrac, frac; - if (a_yr == 0) + if (a_year == 0) { // There is no year 0 in the Julian system! return -1; } - if ((a_yr == 1582) && (a_mo == 10) && (a_day > 4) && (a_day < 15) && (a_bEra == ERA_BCE)) + if (a_year == 1582 && a_month == 10 && a_day > 4 && a_day < 15 && a_era == ERA_BCE) { // The dates 5 through 14 October, 1582 // do not exist in the Gregorian system! return -1; } - tmpyr = a_yr; + tmpyr = a_year; - if (a_bEra == ERA_BCE) + if (a_era == ERA_BCE) { tmpyr = 1 - tmpyr; } - if (a_mo > 2) + if (a_month > 2) { jy = tmpyr; - jm = a_mo + 1; + jm = a_month + 1; } else { jy = tmpyr - 1; - jm = a_mo + 13; + jm = a_month + 13; } intgr = floor(floor(365.25 * jy) + floor(30.6001 * jm) + a_day + 1720995); // check for switch to Gregorian calendar gregcal = 15 + 31 * (10 + 12 * 1582); - if (a_day + 31 * (a_mo + 12 * tmpyr) >= gregcal) + if (a_day + 31 * (a_month + 12 * tmpyr) >= gregcal) { ja = floor(0.01 * jy); intgr += 2 - ja + floor(0.25 * ja); } // correct for half-day offset - dayfrac = a_hr / 24.0 - 0.5; + dayfrac = a_hour / 24.0 - 0.5; if (dayfrac < 0.0) { dayfrac += 1.0; @@ -123,14 +132,14 @@ int tmCalendarToJulian(TimeEra a_bEra, } // now set the fraction of a day - frac = dayfrac + (a_min + a_sec / 60.0) / 60.0 / 24.0; + frac = dayfrac + (a_minute + a_second / 60.0) / 60.0 / 24.0; // round to the nearest second // we don't want to round *a_julian = intgr + frac; return 1; -} // tmCalendarToJulian +} // iCalendarToJulian //------------------------------------------------------------------------------ /// \brief Convert a Julian date and time To calendar date and time. /// @@ -146,23 +155,23 @@ int tmCalendarToJulian(TimeEra a_bEra, /// 3450 Massachusetts Ave, NW /// Washington, DC 20392-5420 \endverbatim */ /// -/// \param a_bEra The returned era: ERA_BCE - BC, ERA_CE - AD. -/// \param a_yr The returned year. -/// \param a_mo The returned month. +/// \param a_era The returned era: ERA_BCE - BC, ERA_CE - AD. +/// \param a_year The returned year. +/// \param a_month The returned month. /// \param a_day The returned day. -/// \param a_hr The returned hour. -/// \param a_min The returned minute. -/// \param a_sec The returned second. +/// \param a_hour The returned hour. +/// \param a_minute The returned minute. +/// \param a_second The returned second. /// \param a_julian The input julian date and time. //------------------------------------------------------------------------------ -void tmJulianToCalendar(TimeEra* a_bEra, - int* a_yr, - int* a_mo, - int* a_day, - int* a_hr, - int* a_min, - int* a_sec, - double a_julian) +void iJulianToCalendar(TimeEra* a_era, + int* a_year, + int* a_month, + int* a_day, + int* a_hour, + int* a_minute, + int* a_second, + double a_julian) { double j1, j2, j3, j4, j5; double intgr, frac, gregjd, tmp; @@ -197,56 +206,113 @@ void tmJulianToCalendar(TimeEra* a_bEra, j5 = floor((j2 - j4) / 30.6001); *a_day = (int)floor(j2 - j4 - floor(j5 * 30.6001)); - *a_mo = (int)floor(j5 - 1); - if (*a_mo > 12) + *a_month = (int)floor(j5 - 1); + if (*a_month > 12) { - *a_mo -= 12; + *a_month -= 12; } - *a_yr = (int)floor(j3 - 4715); - if (*a_mo > 2) + *a_year = (int)floor(j3 - 4715); + if (*a_month > 2) { - --*a_yr; + --*a_year; } - if (*a_yr <= 0) + if (*a_year <= 0) { - --*a_yr; + --*a_year; } // get time of day from day fraction - *a_hr = (int)floor(dayfrac * 24.0); - *a_min = (int)floor((dayfrac * 24.0 - *a_hr) * 60.0); - f = ((dayfrac * 24.0 - *a_hr) * 60.0 - *a_min) * 60.0; - *a_sec = (int)floor(f); - f -= *a_sec; + *a_hour = (int)floor(dayfrac * 24.0); + *a_minute = (int)floor((dayfrac * 24.0 - *a_hour) * 60.0); + f = ((dayfrac * 24.0 - *a_hour) * 60.0 - *a_minute) * 60.0; + *a_second = (int)floor(f); + f -= *a_second; if (f > 0.5) { - ++*a_sec; + ++*a_second; } - if (*a_sec == 60) + if (*a_second == 60) { - *a_sec = 0; - ++*a_min; + *a_second = 0; + ++*a_minute; } - if (*a_min == 60) + if (*a_minute == 60) { - *a_min = 0; - ++*a_hr; + *a_minute = 0; + ++*a_hour; } - if (*a_hr == 24) + if (*a_hour == 24) { - *a_hr = 0; + *a_hour = 0; ++*a_day; } - if (*a_yr < 0) + if (*a_year < 0) { - *a_yr = -*a_yr; - *a_bEra = ERA_BCE; + *a_year = -*a_year; + *a_era = ERA_BCE; } else { - *a_bEra = ERA_CE; + *a_era = ERA_CE; } +} // iJulianToCalendar +} // namespace +//------------------------------------------------------------------------------ +/// \brief Convert a calendar date and time to Julian date and time. +/// \param a_year The input year. +/// \param a_month The input month. +/// \param a_day The input day. +/// \param a_hour The input hour. +/// \param a_minute The input minute. +/// \param a_second The input second. +/// \param a_julian The output julian date and time. +/// \return Returns true if successful. +//------------------------------------------------------------------------------ +bool tmCalendarToJulian(int a_year, + int a_month, + int a_day, + int a_hour, + int a_minute, + int a_second, + double* a_julian) +{ + int result = iCalendarToJulian(ERA_CE, a_year, a_month, a_day, a_hour, a_minute, a_second, a_julian); + if (result <= 0) + return false; + return true; +} // tmCalendarToJulian +//------------------------------------------------------------------------------ +/// \brief Convert a Julian date and time To calendar date and time. +/// \param a_year The returned year. +/// \param a_month The returned month. +/// \param a_day The returned day. +/// \param a_hour The returned hour. +/// \param a_minute The returned minute. +/// \param a_second The returned second. +/// \param a_julian The input julian date and time. +/// \return Returns true if successful. +//------------------------------------------------------------------------------ +bool tmJulianToCalendar(int* a_year, + int* a_month, + int* a_day, + int* a_hour, + int* a_minute, + int* a_second, + double a_julian) +{ + TimeEra era = ERA_BCE; + iJulianToCalendar(&era, a_year, a_month, a_day, a_hour, a_minute, a_second, a_julian); + if (era == ERA_BCE) + return false; + + boost::gregorian::date date(*a_year, *a_month, *a_day); + boost::posix_time::time_duration timeDuration(*a_hour, *a_minute, *a_second, 0); + boost::posix_time::ptime dt(date, timeDuration); + if (dt.is_special()) + return false; + + return true; } // tmJulianToCalendar } // namespace xms @@ -265,61 +331,107 @@ using namespace xms; //------------------------------------------------------------------------------ void TimeConversionUnitTests::testJulianConversions() { - TimeEra era1, era2, era3, era4; - int yr1, mo1, day1, hr1, min1, sec1; - int yr2, mo2, day2, hr2, min2, sec2; - int yr3, mo3, day3, hr3, min3, sec3; - int yr4, mo4, day4, hr4, min4, sec4, result; - double julian1, julian2, julian3, julian4; - - std::ostringstream out; - out << "Calendar conversion:\n\n"; - - era1 = ERA_BCE; - yr1 = mo1 = day1 = hr1 = min1 = sec1 = 0; - julian1 = 2655.5; - tmJulianToCalendar(&era1, &yr1, &mo1, &day1, &hr1, &min1, &sec1, julian1); - - yr2 = 4706; - mo2 = 4; - day2 = 10; - era2 = ERA_BCE; - hr2 = min2 = sec2 = 0; - julian2 = 0.0; - result = tmCalendarToJulian(era2, yr2, mo2, day2, hr2, min2, sec2, &julian2); - TS_ASSERT_EQUALS(1, result); - - era3 = ERA_CE; - yr3 = 2004; - mo3 = 6; - day3 = 3; - hr3 = 2; - min3 = 8; - sec3 = 32; - julian3 = 0.0; - result = tmCalendarToJulian(era3, yr3, mo3, day3, hr3, min3, sec3, &julian3); - TS_ASSERT_EQUALS(1, result); - - era4 = ERA_BCE; - yr4 = mo4 = day4 = hr4 = min4 = sec4 = 0; - julian4 = 2453159.5892592594; - tmJulianToCalendar(&era4, &yr4, &mo4, &day4, &hr4, &min4, &sec4, julian4); - - TS_ASSERT_EQUALS(era1, era2); - TS_ASSERT_EQUALS(yr1, yr2); - TS_ASSERT_EQUALS(mo1, mo2); - TS_ASSERT_EQUALS(day1, day2); - TS_ASSERT_EQUALS(hr1, hr2); - TS_ASSERT_EQUALS(min1, min2); - TS_ASSERT_DELTA(julian1, julian2, 1.0e-5); - - TS_ASSERT_EQUALS(era3, era4); - TS_ASSERT_EQUALS(yr3, yr4); - TS_ASSERT_EQUALS(mo3, mo4); - TS_ASSERT_EQUALS(day3, day4); - TS_ASSERT_EQUALS(hr3, hr4); - TS_ASSERT_EQUALS(min3, min4); - TS_ASSERT_DELTA(julian3, julian4, 1.0e-5); + TimeEra era1, era2, era3, era4; + int yr1, mo1, day1, hr1, min1, sec1; + int yr2, mo2, day2, hr2, min2, sec2; + int yr3, mo3, day3, hr3, min3, sec3; + int yr4, mo4, day4, hr4, min4, sec4, result; + double julian1, julian2, julian3, julian4; + + std::ostringstream out; + out << "Calendar conversion:\n\n"; + + era1 = ERA_BCE; + yr1 = mo1 = day1 = hr1 = min1 = sec1 = 0; + julian1 = 2655.5; + iJulianToCalendar(&era1, &yr1, &mo1, &day1, &hr1, &min1, &sec1, julian1); + + yr2 = 4706; + mo2 = 4; + day2 = 10; + era2 = ERA_BCE; + hr2 = min2 = sec2 = 0; + julian2 = 0.0; + result = iCalendarToJulian(era2, yr2, mo2, day2, hr2, min2, sec2, &julian2); + TS_ASSERT_EQUALS(1, result); + + era3 = ERA_CE; + yr3 = 2004; + mo3 = 6; + day3 = 3; + hr3 = 2; + min3 = 8; + sec3 = 32; + julian3 = 0.0; + result = iCalendarToJulian(era3, yr3, mo3, day3, hr3, min3, sec3, &julian3); + TS_ASSERT_EQUALS(1, result); + + era4 = ERA_BCE; + yr4 = mo4 = day4 = hr4 = min4 = sec4 = 0; + julian4 = 2453159.5892592594; + iJulianToCalendar(&era4, &yr4, &mo4, &day4, &hr4, &min4, &sec4, julian4); + + TS_ASSERT_EQUALS(era1, era2); + TS_ASSERT_EQUALS(yr1, yr2); + TS_ASSERT_EQUALS(mo1, mo2); + TS_ASSERT_EQUALS(day1, day2); + TS_ASSERT_EQUALS(hr1, hr2); + TS_ASSERT_EQUALS(min1, min2); + TS_ASSERT_DELTA(julian1, julian2, 1.0e-5); + + TS_ASSERT_EQUALS(era3, era4); + TS_ASSERT_EQUALS(yr3, yr4); + TS_ASSERT_EQUALS(mo3, mo4); + TS_ASSERT_EQUALS(day3, day4); + TS_ASSERT_EQUALS(hr3, hr4); + TS_ASSERT_EQUALS(min3, min4); + TS_ASSERT_DELTA(julian3, julian4, 1.0e-5); } // TimeConversionUnitTests::testJulianConversions +//------------------------------------------------------------------------------ +/// \brief Test julian to calendar date conversion. +//------------------------------------------------------------------------------ +void TimeConversionUnitTests::testJulianToCalendar() +{ + int year, month, day, hour, minute, second; + TS_ASSERT(tmJulianToCalendar(&year, &month, &day, &hour, &minute, &second, 2447727.5430902778)); + TS_ASSERT_EQUALS(1989, year); + TS_ASSERT_EQUALS(7, month); + TS_ASSERT_EQUALS(20, day); + TS_ASSERT_EQUALS(1, hour); + TS_ASSERT_EQUALS(2, minute); + TS_ASSERT_EQUALS(3, second); +} // TimeConversionUnitTests::testJulianToCalendar +//------------------------------------------------------------------------------ +/// \brief Test julian to calendar date conversion with invalid julian date. +//------------------------------------------------------------------------------ +void TimeConversionUnitTests::testJulianToCalendarInvalid() +{ + int year, month, day, hour, minute, second; + TS_ASSERT(!tmJulianToCalendar(&year, &month, &day, &hour, &minute, &second, -1)); + TS_ASSERT(!tmJulianToCalendar(&year, &month, &day, &hour, &minute, &second, -99999999)); +} // TimeConversionUnitTests::testJulianToCalendarInvalid +//------------------------------------------------------------------------------ +/// \brief Test calendar to julian date conversion. +//------------------------------------------------------------------------------ +void TimeConversionUnitTests::testCalendarToJulian() +{ + double julian; + TS_ASSERT(tmCalendarToJulian(1989, 7, 20, 1, 2, 3, &julian)); + TS_ASSERT_DELTA(2447727.5430902778, julian, 1.0e-5); +} // TimeConversionUnitTests::testCalendarToJulian +//------------------------------------------------------------------------------ +/// \brief Test calendar to julian date conversion with invalid values. +//------------------------------------------------------------------------------ +void TimeConversionUnitTests::testCalendarToJulianInvalid() +{ + double julian; + TS_ASSERT(!tmCalendarToJulian(0, 7, 20, 1, 2, 3, &julian)); + TS_ASSERT_EQUALS(1, iCalendarToJulian(ERA_BCE, 1582, 10, 4, 0, 0, 0, &julian)); + TS_ASSERT_EQUALS(-1, iCalendarToJulian(ERA_BCE, 1582, 10, 5, 0, 0, 0, &julian)); + TS_ASSERT_EQUALS(-1, iCalendarToJulian(ERA_BCE, 1582, 10, 14, 0, 0, 0, &julian)); + TS_ASSERT_EQUALS(1, iCalendarToJulian(ERA_BCE, 1582, 10, 15, 0, 0, 0, &julian)); + // seems like this should also be true but it wasn't like this in XMDF code + // TS_ASSERT_EQUALS(-1, iCalendarToJulian(ERA_CE, 1582, 10, 5, 0, 0, 0, &julian)); +} // TimeConversionUnitTests::testCalendarToJulianInvalid #endif diff --git a/xmscore/time/TimeConversion.h b/xmscore/time/TimeConversion.h index df894fed..a46581bf 100644 --- a/xmscore/time/TimeConversion.h +++ b/xmscore/time/TimeConversion.h @@ -20,24 +20,18 @@ namespace xms { -enum TimeEra { ERA_BCE, ///< Time era BCE (BC) - ERA_CE ///< Time era CE (AD) -}; - -int tmCalendarToJulian(TimeEra a_bEra, - int a_yr, - int a_mo, - int a_day, - int a_hr, - int a_min, - int a_sec, - double* a_julian); -void tmJulianToCalendar(TimeEra* a_bEra, - int* a_yr, - int* a_mo, +bool tmCalendarToJulian(int a_year, + int a_month, + int a_day, + int a_hour, + int a_minute, + int a_second, + double* a_julian); +bool tmJulianToCalendar(int* a_year, + int* a_month, int* a_day, - int* a_hr, - int* a_min, - int* a_sec, + int* a_hour, + int* a_minute, + int* a_second, double a_julian); } // namespace xms diff --git a/xmscore/time/TimeConversion.t.h b/xmscore/time/TimeConversion.t.h index cbb100f7..3554aa8c 100644 --- a/xmscore/time/TimeConversion.t.h +++ b/xmscore/time/TimeConversion.t.h @@ -25,6 +25,10 @@ class TimeConversionUnitTests : public CxxTest::TestSuite { public: void testJulianConversions(); + void testJulianToCalendar(); + void testJulianToCalendarInvalid(); + void testCalendarToJulian(); + void testCalendarToJulianInvalid(); }; #endif