Skip to content

Commit 158aaca

Browse files
committed
Added toString() to temporal types
It returns ISO strings for every temporal type except DateTime with time zone ID. Later is formatted to an ISO date-time and time zone ID is appended in square brackets. Ideally driver would also include offset but there is no way to get it without a third-party library. Offset might be included in future.
1 parent 15cef8d commit 158aaca

File tree

3 files changed

+164
-0
lines changed

3 files changed

+164
-0
lines changed

src/v1/internal/temporal-util.js

+90
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,86 @@ export function epochDayToCypherDate(epochDay) {
169169
return new CypherDate(year, month, day);
170170
}
171171

172+
/**
173+
* Format given duration to an ISO 8601 string.
174+
* @param {Integer|number} months the number of months.
175+
* @param {Integer|number} days the number of days.
176+
* @param {Integer|number} seconds the number of seconds.
177+
* @param {Integer|number} nanoseconds the number of nanoseconds.
178+
* @return {string} ISO string that represents given duration.
179+
*/
180+
export function durationToIsoString(months, days, seconds, nanoseconds) {
181+
const monthsString = formatNumber(months);
182+
const daysString = formatNumber(days);
183+
const secondsString = formatNumber(seconds);
184+
const nanosecondsString = formatNumber(nanoseconds, 9);
185+
return `P${monthsString}M${daysString}DT${secondsString}.${nanosecondsString}S`;
186+
}
187+
188+
/**
189+
* Formats given time to an ISO 8601 string.
190+
* @param {Integer|number} hour the hour value.
191+
* @param {Integer|number} minute the minute value.
192+
* @param {Integer|number} second the second value.
193+
* @param {Integer|number} nanosecond the nanosecond value.
194+
* @return {string} ISO string that represents given time.
195+
*/
196+
export function timeToIsoString(hour, minute, second, nanosecond) {
197+
const hourString = formatNumber(hour, 2);
198+
const minuteString = formatNumber(minute, 2);
199+
const secondString = formatNumber(second, 2);
200+
const nanosecondString = formatNumber(nanosecond, 9);
201+
return `${hourString}:${minuteString}:${secondString}.${nanosecondString}`;
202+
}
203+
204+
/**
205+
* Formats given time zone offset in seconds to string representation like '±HH:MM', '±HH:MM:SS' or 'Z' for UTC.
206+
* @param {Integer|number} offsetSeconds the offset in seconds.
207+
* @return {string} ISO string that represents given offset.
208+
*/
209+
export function timeZoneOffsetToIsoString(offsetSeconds) {
210+
offsetSeconds = int(offsetSeconds);
211+
if (offsetSeconds.equals(0)) {
212+
return 'Z';
213+
}
214+
215+
const isNegative = offsetSeconds.isNegative();
216+
if (isNegative) {
217+
offsetSeconds = offsetSeconds.multiply(-1);
218+
}
219+
const signPrefix = isNegative ? '-' : '+';
220+
221+
const hours = formatNumber(offsetSeconds.div(SECONDS_PER_HOUR), 2);
222+
const minutes = formatNumber(offsetSeconds.div(SECONDS_PER_MINUTE).modulo(MINUTES_PER_HOUR), 2);
223+
let secondsValue = offsetSeconds.modulo(SECONDS_PER_MINUTE);
224+
const seconds = secondsValue.equals(0) ? null : formatNumber(secondsValue, 2);
225+
226+
return seconds ? `${signPrefix}${hours}:${minutes}:${seconds}` : `${signPrefix}${hours}:${minutes}`;
227+
}
228+
229+
/**
230+
* Formats given date to an ISO 8601 string.
231+
* @param {Integer|number} year the date year.
232+
* @param {Integer|number} month the date month.
233+
* @param {Integer|number} day the date day.
234+
* @return {string} ISO string that represents given date.
235+
*/
236+
export function dateToIsoString(year, month, day) {
237+
year = int(year);
238+
const isNegative = year.isNegative();
239+
if (isNegative) {
240+
year = year.multiply(-1);
241+
}
242+
let yearString = year.toString().padStart(4, '0');
243+
if (isNegative) {
244+
yearString = '-' + yearString;
245+
}
246+
247+
const monthString = formatNumber(month, 2);
248+
const dayString = formatNumber(day, 2);
249+
return `${yearString}-${monthString}-${dayString}`;
250+
}
251+
172252
/**
173253
* Converts given cypher local time into a single integer representing this same time in seconds of the day. Nanoseconds are skipped.
174254
* @param {CypherLocalTime} localTime the time to convert.
@@ -231,3 +311,13 @@ function floorMod(x, y) {
231311

232312
return x.subtract(floorDiv(x, y).multiply(y));
233313
}
314+
315+
/**
316+
* @param {Integer|number} num the number to format.
317+
* @param {number} [stringLength=undefined] the string length to left-pad to.
318+
* @return {string} formatted and possibly left-padded number as string.
319+
*/
320+
function formatNumber(num, stringLength = undefined) {
321+
const result = int(num).toString();
322+
return stringLength ? result.padStart(stringLength, '0') : result;
323+
}

src/v1/temporal-types.js

+30
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* limitations under the License.
1818
*/
1919

20+
import {dateToIsoString, durationToIsoString, timeToIsoString, timeZoneOffsetToIsoString} from './internal/temporal-util';
21+
2022
const IDENTIFIER_PROPERTY_ATTRIBUTES = {
2123
value: true,
2224
enumerable: false,
@@ -51,6 +53,10 @@ export class CypherDuration {
5153
this.nanoseconds = nanoseconds;
5254
Object.freeze(this);
5355
}
56+
57+
toString() {
58+
return durationToIsoString(this.months, this.days, this.seconds, this.nanoseconds);
59+
}
5460
}
5561

5662
Object.defineProperty(CypherDuration.prototype, CYPHER_DURATION_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
@@ -84,6 +90,10 @@ export class CypherLocalTime {
8490
this.nanosecond = nanosecond;
8591
Object.freeze(this);
8692
}
93+
94+
toString() {
95+
return timeToIsoString(this.hour, this.minute, this.second, this.nanosecond);
96+
}
8797
}
8898

8999
Object.defineProperty(CypherLocalTime.prototype, CYPHER_LOCAL_TIME_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
@@ -113,6 +123,10 @@ export class CypherTime {
113123
this.offsetSeconds = offsetSeconds;
114124
Object.freeze(this);
115125
}
126+
127+
toString() {
128+
return this.localTime.toString() + timeZoneOffsetToIsoString(this.offsetSeconds);
129+
}
116130
}
117131

118132
Object.defineProperty(CypherTime.prototype, CYPHER_TIME_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
@@ -144,6 +158,10 @@ export class CypherDate {
144158
this.day = day;
145159
Object.freeze(this);
146160
}
161+
162+
toString() {
163+
return dateToIsoString(this.year, this.month, this.day);
164+
}
147165
}
148166

149167
Object.defineProperty(CypherDate.prototype, CYPHER_DATE_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
@@ -173,6 +191,10 @@ export class CypherLocalDateTime {
173191
this.localTime = localTime;
174192
Object.freeze(this);
175193
}
194+
195+
toString() {
196+
return `${this.localDate.toString()}T${this.localTime.toString()}`;
197+
}
176198
}
177199

178200
Object.defineProperty(CypherLocalDateTime.prototype, CYPHER_LOCAL_DATE_TIME_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
@@ -202,6 +224,10 @@ export class CypherDateTimeWithZoneOffset {
202224
this.offsetSeconds = offsetSeconds;
203225
Object.freeze(this);
204226
}
227+
228+
toString() {
229+
return this.localDateTime.toString() + timeZoneOffsetToIsoString(this.offsetSeconds);
230+
}
205231
}
206232

207233
Object.defineProperty(CypherDateTimeWithZoneOffset.prototype, CYPHER_DATE_TIME_WITH_ZONE_OFFSET_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);
@@ -231,6 +257,10 @@ export class CypherDateTimeWithZoneId {
231257
this.zoneId = zoneId;
232258
Object.freeze(this);
233259
}
260+
261+
toString() {
262+
return `${this.localDateTime.toString()}[${this.zoneId}]`;
263+
}
234264
}
235265

236266
Object.defineProperty(CypherDateTimeWithZoneId.prototype, CYPHER_DATE_TIME_WITH_ZONE_ID_IDENTIFIER_PROPERTY, IDENTIFIER_PROPERTY_ATTRIBUTES);

test/v1/temporal-types.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,50 @@ describe('temporal-types', () => {
342342
testSendAndReceiveRandomTemporalValues(valueGenerator, done);
343343
});
344344

345+
it('should convert Duration to ISO string', () => {
346+
expect(duration(13, 62, 3, 999111999).toString()).toEqual('P13M62DT3.999111999S');
347+
expect(duration(0, 0, 0, 0).toString()).toEqual('P0M0DT0.000000000S');
348+
expect(duration(-1, -2, 10, 10).toString()).toEqual('P-1M-2DT10.000000010S');
349+
});
350+
351+
it('should convert LocalTime to ISO string', () => {
352+
expect(localTime(12, 19, 39, 111222333).toString()).toEqual('12:19:39.111222333');
353+
expect(localTime(3, 59, 2, 17).toString()).toEqual('03:59:02.000000017');
354+
expect(localTime(0, 0, 0, 0).toString()).toEqual('00:00:00.000000000');
355+
});
356+
357+
it('should convert Time to ISO string', () => {
358+
expect(time(11, 45, 22, 333222111, 9015).toString()).toEqual('11:45:22.333222111+02:30:15');
359+
expect(time(23, 2, 1, 10, 0).toString()).toEqual('23:02:01.000000010Z');
360+
expect(time(0, 12, 59, 0, -40500).toString()).toEqual('00:12:59.000000000-11:15');
361+
expect(time(21, 59, 0, 123, -25200).toString()).toEqual('21:59:00.000000123-07:00');
362+
});
363+
364+
it('should convert Date to ISO string', () => {
365+
expect(date(2015, 10, 12).toString()).toEqual('2015-10-12');
366+
expect(date(881, 1, 1).toString()).toEqual('0881-01-01');
367+
expect(date(-999, 12, 24).toString()).toEqual('-0999-12-24');
368+
expect(date(-9, 1, 1).toString()).toEqual('-0009-01-01');
369+
});
370+
371+
it('should convert LocalDateTime to ISO string', () => {
372+
expect(localDateTime(1992, 11, 8, 9, 42, 17, 22).toString()).toEqual('1992-11-08T09:42:17.000000022');
373+
expect(localDateTime(-10, 7, 15, 8, 15, 33, 500).toString()).toEqual('-0010-07-15T08:15:33.000000500');
374+
expect(localDateTime(0, 0, 0, 0, 0, 0, 1).toString()).toEqual('0000-00-00T00:00:00.000000001');
375+
});
376+
377+
it('should convert DateTime with time zone offset to ISO string', () => {
378+
expect(dateTimeWithZoneOffset(2025, 9, 17, 23, 22, 21, 999888, 37800).toString()).toEqual('2025-09-17T23:22:21.000999888+10:30');
379+
expect(dateTimeWithZoneOffset(1, 2, 3, 4, 5, 6, 7, -49376).toString()).toEqual('0001-02-03T04:05:06.000000007-13:42:56');
380+
expect(dateTimeWithZoneOffset(-3, 3, 9, 9, 33, 27, 999000, 15300).toString()).toEqual('-0003-03-09T09:33:27.000999000+04:15');
381+
});
382+
383+
it('should convert DateTime with time zone id to ISO-like string', () => {
384+
expect(dateTimeWithZoneId(1949, 10, 7, 6, 10, 15, 15000000, 'Europe/Zaporozhye').toString()).toEqual('1949-10-07T06:10:15.015000000[Europe/Zaporozhye]');
385+
expect(dateTimeWithZoneId(-30455, 5, 5, 12, 24, 10, 123, 'Asia/Yangon').toString()).toEqual('-30455-05-05T12:24:10.000000123[Asia/Yangon]');
386+
expect(dateTimeWithZoneId(248, 12, 30, 23, 59, 59, 3, 'CET').toString()).toEqual('0248-12-30T23:59:59.000000003[CET]');
387+
});
388+
345389
function testSendAndReceiveRandomTemporalValues(valueGenerator, done) {
346390
const asyncFunction = (index, callback) => {
347391
const next = () => callback();

0 commit comments

Comments
 (0)