Skip to content

Commit

Permalink
Merge pull request #12345 from jfayot/fix_11507
Browse files Browse the repository at this point in the history
Fixes issue #11507 (JulianDate.toIso8601 builds a non compliant string with very small milliseconds)
  • Loading branch information
ggetz authored Dec 9, 2024
2 parents c72ad38 + c5951b7 commit dcaf957
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 9 deletions.
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

0 comments on commit dcaf957

Please sign in to comment.