Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes issue #11507 (JulianDate.toIso8601 builds a non compliant string with very small milliseconds) #12345

Merged
merged 7 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

### 1.125 - 2025-01-02

##### Fixes :wrench:

- Fixed JulianDate to always generate valid ISO strings for fractional milliseconds [#12345](https://github.com/CesiumGS/cesium/pull/12345)

### 1.124 - 2024-12-02

#### @cesium/engine
Expand Down
21 changes: 14 additions & 7 deletions packages/engine/Source/Core/JulianDate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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++;
Expand All @@ -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.
Expand All @@ -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;
}

Expand Down Expand Up @@ -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 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 capturing 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
Expand Down
36 changes: 34 additions & 2 deletions packages/engine/Specs/Core/JulianDateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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");
Expand Down
Loading