From ddd86d6532c6edb424260a3655e9b8068b81221b Mon Sep 17 00:00:00 2001 From: "jerome.fayot" Date: Thu, 28 Nov 2024 11:30:41 +0100 Subject: [PATCH 1/4] fix: fixes issue #11507 (JulianDate.toIso8601 builds a non compliant string with very small milliseconds) --- packages/engine/Source/Core/JulianDate.js | 21 ++++++++----- packages/engine/Specs/Core/JulianDateSpec.js | 32 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/engine/Source/Core/JulianDate.js b/packages/engine/Source/Core/JulianDate.js index 092e4683e5a8..14942c78b64f 100644 --- a/packages/engine/Source/Core/JulianDate.js +++ b/packages/engine/Source/Core/JulianDate.js @@ -10,7 +10,7 @@ import TimeStandard from "./TimeStandard.js"; const gregorianDateScratch = new GregorianDate(); const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; -const daysInLeapFeburary = 29; +const daysInLeapFebruary = 29; function compareLeapSecondDates(leapSecond, dateToFind) { return JulianDate.compare(leapSecond.julianDate, dateToFind.julianDate); @@ -430,7 +430,7 @@ JulianDate.fromIso8601 = function (iso8601String, result) { month > 12 || day < 1 || ((month !== 2 || !inLeapYear) && day > daysInMonth[month - 1]) || - (inLeapYear && month === 2 && day > daysInLeapFeburary) + (inLeapYear && month === 2 && day > daysInLeapFebruary) ) { throw new DeveloperError(iso8601ErrorMessage); } @@ -542,7 +542,7 @@ JulianDate.fromIso8601 = function (iso8601String, result) { day++; } - tmp = inLeapYear && month === 2 ? daysInLeapFeburary : daysInMonth[month - 1]; + tmp = inLeapYear && month === 2 ? daysInLeapFebruary : daysInMonth[month - 1]; while (day > tmp) { day -= tmp; month++; @@ -553,7 +553,7 @@ JulianDate.fromIso8601 = function (iso8601String, result) { } tmp = - inLeapYear && month === 2 ? daysInLeapFeburary : daysInMonth[month - 1]; + inLeapYear && month === 2 ? daysInLeapFebruary : daysInMonth[month - 1]; } //If UTC offset is at the beginning/end of the day, minutes can be negative. @@ -575,7 +575,7 @@ JulianDate.fromIso8601 = function (iso8601String, result) { } tmp = - inLeapYear && month === 2 ? daysInLeapFeburary : daysInMonth[month - 1]; + inLeapYear && month === 2 ? daysInLeapFebruary : daysInMonth[month - 1]; day += tmp; } @@ -784,8 +784,15 @@ JulianDate.toIso8601 = function (julianDate, precision) { let millisecondStr; if (!defined(precision) && millisecond !== 0) { - //Forces milliseconds into a number with at least 3 digits to whatever the default toString() precision is. - millisecondStr = (millisecond * 0.01).toString().replace(".", ""); + //Forces milliseconds into a number with at least 3 digits. + const millisecondHundreds = millisecond * 0.01; + // Below 1e-6 toString uses scientific notation, so we need replace toString by toFixed. + // 20 digits is a trade-off choice guided by JavaScript's Number representation accuracy (15-17 decimal digits for most numbers). + // Using toFixed(20) ensures we capture enough precision while avoiding inaccuracies due to floating-point limitations. + millisecondStr = + millisecondHundreds < 1e-6 + ? millisecondHundreds.toFixed(20).replace(".", "").replace(/0+$/, "") + : millisecondHundreds.toString().replace(".", ""); return `${year.toString().padStart(4, "0")}-${month .toString() .padStart(2, "0")}-${day.toString().padStart(2, "0")}T${hour diff --git a/packages/engine/Specs/Core/JulianDateSpec.js b/packages/engine/Specs/Core/JulianDateSpec.js index 9bc22b32d88e..477270198020 100644 --- a/packages/engine/Specs/Core/JulianDateSpec.js +++ b/packages/engine/Specs/Core/JulianDateSpec.js @@ -877,6 +877,38 @@ describe("Core/JulianDate", function () { expect(date).toEqual("0950-01-02T03:04:05.0123450Z"); }); + it("toIso8601 works with very small milliseconds", function () { + let expectedDate, date; + + expectedDate = new JulianDate(2450630, 1e-3); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-4); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-6); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-7); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-8); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-10); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-15); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-18); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + expectedDate = new JulianDate(2450630, 1e-21); + date = JulianDate.fromIso8601(JulianDate.toIso8601(expectedDate)); + expect(date).toEqual(expectedDate); + }); + it("can format Iso8601.MINIMUM_VALUE and MAXIMUM_VALUE to ISO strings", function () { const minString = Iso8601.MINIMUM_VALUE.toString(); expect(minString).toEqual("0000-01-01T00:00:00Z"); From 16dfdc84a5db8631b5ddf113633ba2aa7d139f6e Mon Sep 17 00:00:00 2001 From: "jerome.fayot" Date: Thu, 28 Nov 2024 11:40:32 +0100 Subject: [PATCH 2/4] doc: updated CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 6c6aca2d2063..b338807383cd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ - Fixed lag or crashes when loading many models in the same frame. [#12320](https://github.com/CesiumGS/cesium/pull/12320) - Updated WMS example URL in UrlTemplateImageryProvider documentation to use an active service. [#12323](https://github.com/CesiumGS/cesium/pull/12323) - Fix point cloud filtering performance on certain hardware [#12317](https://github.com/CesiumGS/cesium/pull/12317) +- Fixed JulianDate to always generate valid ISO strings for fractional milliseconds [#12345](https://github.com/CesiumGS/cesium/pull/12345) ##### Deprecated :hourglass_flowing_sand: From c40083f67c43e39eaff2a31b3d6244b273a8a1b7 Mon Sep 17 00:00:00 2001 From: "jerome.fayot" Date: Tue, 3 Dec 2024 09:21:16 +0100 Subject: [PATCH 3/4] doc: comments improvements --- packages/engine/Source/Core/JulianDate.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Core/JulianDate.js b/packages/engine/Source/Core/JulianDate.js index 14942c78b64f..ec9bfe09da86 100644 --- a/packages/engine/Source/Core/JulianDate.js +++ b/packages/engine/Source/Core/JulianDate.js @@ -784,11 +784,11 @@ JulianDate.toIso8601 = function (julianDate, precision) { let millisecondStr; if (!defined(precision) && millisecond !== 0) { - //Forces milliseconds into a number with at least 3 digits. + // Forces milliseconds into a number with at least 3 digits. const millisecondHundreds = millisecond * 0.01; - // Below 1e-6 toString uses scientific notation, so we need replace toString by toFixed. + // Below 1e-6, toString returns scientific notation, so it should be replaced by toFixed with appropriate number of digits. // 20 digits is a trade-off choice guided by JavaScript's Number representation accuracy (15-17 decimal digits for most numbers). - // Using toFixed(20) ensures we capture enough precision while avoiding inaccuracies due to floating-point limitations. + // Using toFixed(20) ensures capturing enough precision while avoiding inaccuracies due to floating-point limitations. millisecondStr = millisecondHundreds < 1e-6 ? millisecondHundreds.toFixed(20).replace(".", "").replace(/0+$/, "") From c3e326414c06c4b5b0e64b35fd27fd9855e9184d Mon Sep 17 00:00:00 2001 From: "jerome.fayot" Date: Tue, 3 Dec 2024 09:25:25 +0100 Subject: [PATCH 4/4] doc: fixed misspelling --- packages/engine/Specs/Core/JulianDateSpec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Specs/Core/JulianDateSpec.js b/packages/engine/Specs/Core/JulianDateSpec.js index 477270198020..03842a7f335f 100644 --- a/packages/engine/Specs/Core/JulianDateSpec.js +++ b/packages/engine/Specs/Core/JulianDateSpec.js @@ -594,13 +594,13 @@ describe("Core/JulianDate", function () { }).toThrowDeveloperError(); }); - it("Fails to construct an ISO8601 Febuary date with more than 28 days", function () { + it("Fails to construct an ISO8601 February date with more than 28 days", function () { expect(function () { return JulianDate.fromIso8601("2009-02-29"); }).toThrowDeveloperError(); }); - it("Fails to construct an ISO8601 Febuary leap year date with more than 29 days", function () { + it("Fails to construct an ISO8601 February leap year date with more than 29 days", function () { expect(function () { return JulianDate.fromIso8601("2000-02-30"); }).toThrowDeveloperError();