From a3e57dd5428464ad9c35ef5b8e49d50b2c85f095 Mon Sep 17 00:00:00 2001 From: dobon Date: Thu, 5 Dec 2024 12:36:47 +0000 Subject: [PATCH 1/9] update precision pull request --- src/datetime.js | 60 ++++++++++++++++++++++++++---------- test/datetime/format.test.js | 30 ++++++++++++++++++ 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/datetime.js b/src/datetime.js index d4b5424cf..0258031bd 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -53,6 +53,12 @@ import Invalid from "./impl/invalid.js"; const INVALID = "Invalid DateTime"; const MAX_DATE = 8.64e15; +const Precision = { + hours: 1, + minutes: 2, + seconds: 3, + milliseconds: 4, +}; function unsupportedZone(zone) { return new Invalid("unsupported zone", `the zone "${zone.name}" is not supported`); @@ -238,25 +244,30 @@ function toISOTime( suppressSeconds, suppressMilliseconds, includeOffset, - extendedZone + extendedZone, + precision ) { + let desiredPrecision = Precision[normalizeUnit(precision)]; + if (desiredPrecision === undefined) throw new InvalidUnitError(precision); + let c = padStart(o.c.hour); - if (extended) { - c += ":"; - c += padStart(o.c.minute); - if (o.c.millisecond !== 0 || o.c.second !== 0 || !suppressSeconds) { - c += ":"; - } - } else { + if (desiredPrecision >= Precision.minutes) { + if (extended) c += ":"; c += padStart(o.c.minute); - } - - if (o.c.millisecond !== 0 || o.c.second !== 0 || !suppressSeconds) { - c += padStart(o.c.second); - if (o.c.millisecond !== 0 || !suppressMilliseconds) { - c += "."; - c += padStart(o.c.millisecond, 3); + if ( + desiredPrecision >= Precision.seconds && + !(suppressSeconds && o.c.millisecond === 0 && o.c.second === 0) + ) { + if (extended) c += ":"; + c += padStart(o.c.second); + + if ( + desiredPrecision >= Precision.milliseconds && + !(suppressMilliseconds && o.c.millisecond === 0) + ) { + c += "." + padStart(o.c.millisecond, 3); + } } } @@ -1819,10 +1830,12 @@ export default class DateTime { * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00' * @param {boolean} [opts.extendedZone=false] - add the time zone format extension * @param {string} [opts.format='extended'] - choose between the basic and extended format + * @param {string} [opts.precision='milliseconds'] - specify desired time precision: 'hours', 'minutes', 'seconds' or 'milliseconds' * @example DateTime.utc(1983, 5, 25).toISO() //=> '1982-05-25T00:00:00.000Z' * @example DateTime.now().toISO() //=> '2017-04-22T20:47:05.335-04:00' * @example DateTime.now().toISO({ includeOffset: false }) //=> '2017-04-22T20:47:05.335' * @example DateTime.now().toISO({ format: 'basic' }) //=> '20170422T204705.335-0400' + * @example DateTime.now().toISO({ precision: 'minute' }) //=> '2017-04-22T20:47Z' * @return {string|null} */ toISO({ @@ -1831,6 +1844,7 @@ export default class DateTime { suppressMilliseconds = false, includeOffset = true, extendedZone = false, + precision = "milliseconds", } = {}) { if (!this.isValid) { return null; @@ -1840,7 +1854,15 @@ export default class DateTime { let c = toISODate(this, ext); c += "T"; - c += toISOTime(this, ext, suppressSeconds, suppressMilliseconds, includeOffset, extendedZone); + c += toISOTime( + this, + ext, + suppressSeconds, + suppressMilliseconds, + includeOffset, + extendedZone, + precision + ); return c; } @@ -1878,10 +1900,12 @@ export default class DateTime { * @param {boolean} [opts.extendedZone=true] - add the time zone format extension * @param {boolean} [opts.includePrefix=false] - include the `T` prefix * @param {string} [opts.format='extended'] - choose between the basic and extended format + * @param {string} [opts.precision='milliseconds'] - specify desired time precision: 'hours', 'minutes', 'seconds' or 'milliseconds' * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime() //=> '07:34:19.361Z' * @example DateTime.utc().set({ hour: 7, minute: 34, seconds: 0, milliseconds: 0 }).toISOTime({ suppressSeconds: true }) //=> '07:34Z' * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime({ format: 'basic' }) //=> '073419.361Z' * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime({ includePrefix: true }) //=> 'T07:34:19.361Z' + * @example DateTime.utc().set({ hour: 7, minute: 34, second: 56 }).toISOTime({ precision: 'minute' }) //=> '07:34Z' * @return {string} */ toISOTime({ @@ -1891,6 +1915,7 @@ export default class DateTime { includePrefix = false, extendedZone = false, format = "extended", + precision = "milliseconds", } = {}) { if (!this.isValid) { return null; @@ -1905,7 +1930,8 @@ export default class DateTime { suppressSeconds, suppressMilliseconds, includeOffset, - extendedZone + extendedZone, + precision ) ); } diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js index d598a38f7..c824d18af 100644 --- a/test/datetime/format.test.js +++ b/test/datetime/format.test.js @@ -1,6 +1,7 @@ /* global test expect */ import { DateTime, Zone, FixedOffsetZone } from "../../src/luxon"; +import { InvalidUnitError } from "../../src/errors"; const dtMaker = () => DateTime.fromObject( @@ -261,6 +262,35 @@ test("DateTime#toISOTime() returns null for invalid DateTimes", () => { expect(invalid.toISOTime()).toBe(null); }); +test("DateTime#toISOTime({precision}) truncates time to desired precision", () => { + expect(dt.toISOTime({ precision: "millisecond" })).toBe("09:23:54.123Z"); + expect(dt.toISOTime({ precision: "second" })).toBe("09:23:54Z"); + expect(dt.toISOTime({ precision: "minute" })).toBe("09:23Z"); + expect(dt.toISOTime({ precision: "hours" })).toBe("09Z"); +}); + +test("DateTime#toISOTime({precision}) throws when the precision is invalid", () => { + expect(() => dt.toISOTime({ precision: "ms" })).toThrow(InvalidUnitError); + expect(() => dt.toISOTime({ precision: "xxx" })).toThrow(InvalidUnitError); + expect(() => dt.toISOTime({ precision: null })).toThrow(InvalidUnitError); + expect(() => dt.toISOTime({ precision: "days" })).toThrow(InvalidUnitError); + expect(() => dt.toISOTime({ precision: "months" })).toThrow(InvalidUnitError); + expect(() => dt.toISOTime({ precision: "years" })).toThrow(InvalidUnitError); +}); + +test("DateTime#toISOTime({precision, suppressSeconds}) precision takes precedence", () => { + const dt2 = dt.set({ second: 0, millisecond: 0 }); + expect(dt2.toISOTime({ precision: "minute", suppressSeconds: true })).toBe("09:23Z"); + expect(dt2.toISOTime({ precision: "second", suppressSeconds: true })).toBe("09:23Z"); + expect(dt2.toISOTime({ precision: "millisecond", suppressSeconds: true })).toBe("09:23Z"); +}); + +test("DateTime#toISOTime({precision, suppressMilliseconds}) supress", () => { + expect( + dt.set({ millisecond: 0 }).toISOTime({ precision: "millisecond", suppressMilliseconds: true }) + ).toBe("09:23:54Z"); +}); + //------ // #toRFC2822() //------ From 708400a60b818974fa0a7202903a7d8c633c686e Mon Sep 17 00:00:00 2001 From: dobon Date: Thu, 5 Dec 2024 04:54:06 -0800 Subject: [PATCH 2/9] fix bugs from tests --- src/datetime.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/datetime.js b/src/datetime.js index 0258031bd..7d04320f8 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -54,10 +54,10 @@ import Invalid from "./impl/invalid.js"; const INVALID = "Invalid DateTime"; const MAX_DATE = 8.64e15; const Precision = { - hours: 1, - minutes: 2, - seconds: 3, - milliseconds: 4, + hour: 1, + minute: 2, + second: 3, + millisecond: 4, }; function unsupportedZone(zone) { @@ -251,21 +251,15 @@ function toISOTime( if (desiredPrecision === undefined) throw new InvalidUnitError(precision); let c = padStart(o.c.hour); - if (desiredPrecision >= Precision.minutes) { + if (desiredPrecision >= Precision.minute) { if (extended) c += ":"; c += padStart(o.c.minute); - if ( - desiredPrecision >= Precision.seconds && - !(suppressSeconds && o.c.millisecond === 0 && o.c.second === 0) - ) { + if (desiredPrecision >= Precision.second && !(suppressSeconds && o.c.millisecond === 0 && o.c.second === 0)) { if (extended) c += ":"; c += padStart(o.c.second); - if ( - desiredPrecision >= Precision.milliseconds && - !(suppressMilliseconds && o.c.millisecond === 0) - ) { + if (desiredPrecision >= Precision.millisecond && !(suppressMilliseconds && o.c.millisecond === 0)) { c += "." + padStart(o.c.millisecond, 3); } } From 0bdd790cf0591a2d7996a569c3d83ef3e8e00049 Mon Sep 17 00:00:00 2001 From: dobon Date: Thu, 5 Dec 2024 05:03:57 -0800 Subject: [PATCH 3/9] appease style rules --- src/datetime.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datetime.js b/src/datetime.js index 7d04320f8..2fe6382e2 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -254,12 +254,12 @@ function toISOTime( if (desiredPrecision >= Precision.minute) { if (extended) c += ":"; c += padStart(o.c.minute); - - if (desiredPrecision >= Precision.second && !(suppressSeconds && o.c.millisecond === 0 && o.c.second === 0)) { + const hideSeconds = suppressSeconds && o.c.millisecond === 0 && o.c.second === 0; + if (desiredPrecision >= Precision.second && !hideSeconds) { if (extended) c += ":"; c += padStart(o.c.second); - - if (desiredPrecision >= Precision.millisecond && !(suppressMilliseconds && o.c.millisecond === 0)) { + const hideMilliseconds = suppressMilliseconds && o.c.millisecond === 0; + if (desiredPrecision >= Precision.millisecond && !hideMilliseconds) { c += "." + padStart(o.c.millisecond, 3); } } From bc566c8f2e07475e86a2889169498f98a1e6f2e1 Mon Sep 17 00:00:00 2001 From: dobon Date: Thu, 5 Dec 2024 05:09:34 -0800 Subject: [PATCH 4/9] fix test --- test/datetime/format.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js index c824d18af..6787dfd0f 100644 --- a/test/datetime/format.test.js +++ b/test/datetime/format.test.js @@ -272,7 +272,7 @@ test("DateTime#toISOTime({precision}) truncates time to desired precision", () = test("DateTime#toISOTime({precision}) throws when the precision is invalid", () => { expect(() => dt.toISOTime({ precision: "ms" })).toThrow(InvalidUnitError); expect(() => dt.toISOTime({ precision: "xxx" })).toThrow(InvalidUnitError); - expect(() => dt.toISOTime({ precision: null })).toThrow(InvalidUnitError); + expect(() => dt.toISOTime({ precision: null })).toThrow(TypeError); expect(() => dt.toISOTime({ precision: "days" })).toThrow(InvalidUnitError); expect(() => dt.toISOTime({ precision: "months" })).toThrow(InvalidUnitError); expect(() => dt.toISOTime({ precision: "years" })).toThrow(InvalidUnitError); From 74e7747528a330d9041004c1398c11d1baa88533 Mon Sep 17 00:00:00 2001 From: dobon Date: Thu, 5 Dec 2024 21:06:52 -0800 Subject: [PATCH 5/9] extend precision + suppress tests --- test/datetime/format.test.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js index 6787dfd0f..2c7b4e8cd 100644 --- a/test/datetime/format.test.js +++ b/test/datetime/format.test.js @@ -278,17 +278,20 @@ test("DateTime#toISOTime({precision}) throws when the precision is invalid", () expect(() => dt.toISOTime({ precision: "years" })).toThrow(InvalidUnitError); }); -test("DateTime#toISOTime({precision, suppressSeconds}) precision takes precedence", () => { +test("DateTime#toISOTime({precision, suppressSeconds}) suppresses when precision is > 'hour'", () => { const dt2 = dt.set({ second: 0, millisecond: 0 }); + expect(dt2.toISOTime({ precision: "hour", suppressSeconds: true })).toBe("09Z"); expect(dt2.toISOTime({ precision: "minute", suppressSeconds: true })).toBe("09:23Z"); expect(dt2.toISOTime({ precision: "second", suppressSeconds: true })).toBe("09:23Z"); expect(dt2.toISOTime({ precision: "millisecond", suppressSeconds: true })).toBe("09:23Z"); }); -test("DateTime#toISOTime({precision, suppressMilliseconds}) supress", () => { - expect( - dt.set({ millisecond: 0 }).toISOTime({ precision: "millisecond", suppressMilliseconds: true }) - ).toBe("09:23:54Z"); +test("DateTime#toISOTime({precision, suppressMilliseconds}) suppresses when precision is > 'minute'", () => { + const dt2 = dt.set({ millisecond: 0 }); + expect(dt2.toISOTime({ precision: "hour", suppressMilliseconds: true })).toBe("09Z"); + expect(dt2.toISOTime({ precision: "minute", suppressMilliseconds: true })).toBe("09:23Z"); + expect(dt2.toISOTime({ precision: "second", suppressMilliseconds: true })).toBe("09:23:54Z"); + expect(dt2.toISOTime({ precision: "millisecond", suppressMilliseconds: true })).toBe("09:23:54Z"); }); //------ From 1711af850df97264fcd3ccb75a39ed8543eb2b8b Mon Sep 17 00:00:00 2001 From: dobon Date: Thu, 5 Dec 2024 22:27:29 -0800 Subject: [PATCH 6/9] replace nested ifs with a fall-through switch case --- src/datetime.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/datetime.js b/src/datetime.js index 2fe6382e2..78e894e7b 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -250,19 +250,18 @@ function toISOTime( let desiredPrecision = Precision[normalizeUnit(precision)]; if (desiredPrecision === undefined) throw new InvalidUnitError(precision); - let c = padStart(o.c.hour); - if (desiredPrecision >= Precision.minute) { - if (extended) c += ":"; - c += padStart(o.c.minute); - const hideSeconds = suppressSeconds && o.c.millisecond === 0 && o.c.second === 0; - if (desiredPrecision >= Precision.second && !hideSeconds) { - if (extended) c += ":"; - c += padStart(o.c.second); - const hideMilliseconds = suppressMilliseconds && o.c.millisecond === 0; - if (desiredPrecision >= Precision.millisecond && !hideMilliseconds) { - c += "." + padStart(o.c.millisecond, 3); - } - } + switch (true) { + case (desiredPrecision >= Precision.hour): + c += padStart(o.c.hour); + case (desiredPrecision >= Precision.minute): + const extendedGlyph = (extended) ? ":" : ""; + c += extendedGlyph + padStart(o.c.minute); + case (desiredPrecision >= Precision.second): + if (suppressSeconds && o.c.millisecond === 0 && o.c.second === 0) break; + c += extendedGlyph + padStart(o.c.second); + case (desiredPrecision >= Precision.millisecond): + if (suppressMilliseconds && o.c.millisecond === 0) break; + c += "." + padStart(o.c.millisecond, 3); } if (includeOffset) { From c9b1ffe6acfae6a78f4066aeba52870164e44699 Mon Sep 17 00:00:00 2001 From: dobon Date: Fri, 6 Dec 2024 06:45:45 +0000 Subject: [PATCH 7/9] appease javascript linter --- src/datetime.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/datetime.js b/src/datetime.js index 78e894e7b..fdebf63c1 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -251,15 +251,15 @@ function toISOTime( if (desiredPrecision === undefined) throw new InvalidUnitError(precision); switch (true) { - case (desiredPrecision >= Precision.hour): + case desiredPrecision >= Precision.hour: c += padStart(o.c.hour); - case (desiredPrecision >= Precision.minute): - const extendedGlyph = (extended) ? ":" : ""; + case desiredPrecision >= Precision.minute: + const extendedGlyph = extended ? ":" : ""; c += extendedGlyph + padStart(o.c.minute); - case (desiredPrecision >= Precision.second): + case desiredPrecision >= Precision.second: if (suppressSeconds && o.c.millisecond === 0 && o.c.second === 0) break; c += extendedGlyph + padStart(o.c.second); - case (desiredPrecision >= Precision.millisecond): + case desiredPrecision >= Precision.millisecond: if (suppressMilliseconds && o.c.millisecond === 0) break; c += "." + padStart(o.c.millisecond, 3); } From 6c0a507ad172822efd8b752dcd474bd9f3b2c063 Mon Sep 17 00:00:00 2001 From: dobon Date: Fri, 6 Dec 2024 08:20:41 +0000 Subject: [PATCH 8/9] fix logic to pass tests --- src/datetime.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/datetime.js b/src/datetime.js index fdebf63c1..0aa16047c 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -247,21 +247,22 @@ function toISOTime( extendedZone, precision ) { - let desiredPrecision = Precision[normalizeUnit(precision)]; + const desiredPrecision = Precision[normalizeUnit(precision)]; if (desiredPrecision === undefined) throw new InvalidUnitError(precision); - - switch (true) { - case desiredPrecision >= Precision.hour: - c += padStart(o.c.hour); - case desiredPrecision >= Precision.minute: - const extendedGlyph = extended ? ":" : ""; - c += extendedGlyph + padStart(o.c.minute); - case desiredPrecision >= Precision.second: - if (suppressSeconds && o.c.millisecond === 0 && o.c.second === 0) break; - c += extendedGlyph + padStart(o.c.second); - case desiredPrecision >= Precision.millisecond: - if (suppressMilliseconds && o.c.millisecond === 0) break; - c += "." + padStart(o.c.millisecond, 3); + const extendedGlyph = extended ? ":" : ""; + const showSeconds = !suppressSeconds || o.c.millisecond !== 0 || o.c.second !== 0; + const showMilliseconds = showSeconds && (!suppressMilliseconds || o.c.millisecond !== 0); + + let c = new String(); + switch (desiredPrecision) { + case Precision.millisecond: + c = showMilliseconds ? "." + padStart(o.c.millisecond, 3) : c; + case Precision.second: + c = showSeconds ? extendedGlyph + padStart(o.c.second) + c : c; + case Precision.minute: + c = extendedGlyph + padStart(o.c.minute) + c; + case Precision.hour: + c = padStart(o.c.hour) + c; } if (includeOffset) { From 1a2d79e151487a1e30b8a3e972c2ee6899e08d33 Mon Sep 17 00:00:00 2001 From: dobon Date: Fri, 6 Dec 2024 11:52:41 +0000 Subject: [PATCH 9/9] simplify logic to remove switch case. --- src/datetime.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/datetime.js b/src/datetime.js index 0aa16047c..506f14ea5 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -250,19 +250,18 @@ function toISOTime( const desiredPrecision = Precision[normalizeUnit(precision)]; if (desiredPrecision === undefined) throw new InvalidUnitError(precision); const extendedGlyph = extended ? ":" : ""; - const showSeconds = !suppressSeconds || o.c.millisecond !== 0 || o.c.second !== 0; - const showMilliseconds = showSeconds && (!suppressMilliseconds || o.c.millisecond !== 0); - - let c = new String(); - switch (desiredPrecision) { - case Precision.millisecond: - c = showMilliseconds ? "." + padStart(o.c.millisecond, 3) : c; - case Precision.second: - c = showSeconds ? extendedGlyph + padStart(o.c.second) + c : c; - case Precision.minute: - c = extendedGlyph + padStart(o.c.minute) + c; - case Precision.hour: - c = padStart(o.c.hour) + c; + const showSeconds = !(suppressSeconds && o.c.millisecond == 0 && o.c.second == 0); + const showMilliseconds = showSeconds && !(suppressMilliseconds && o.c.millisecond == 0); + + let c = padStart(o.c.hour); + if (desiredPrecision >= Precision.minute) { + c += extendedGlyph + padStart(o.c.minute); + } + if (desiredPrecision >= Precision.second && showSeconds) { + c += extendedGlyph + padStart(o.c.second); + } + if (desiredPrecision >= Precision.millisecond && showMilliseconds) { + c += "." + padStart(o.c.millisecond, 3); } if (includeOffset) {