Skip to content

Commit

Permalink
Add 'calendar' option to toString() methods
Browse files Browse the repository at this point in the history
The 'calendar' option takes values 'auto', 'always', and 'never', and
controls whether to display the calendar in toString() for all the
calendar types.

See: #703
  • Loading branch information
ptomato committed Oct 30, 2020
1 parent 00fac60 commit 19a9248
Show file tree
Hide file tree
Showing 24 changed files with 292 additions and 63 deletions.
14 changes: 13 additions & 1 deletion docs/date.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,13 +555,25 @@ date.equals(other); // => false
date.equals(date); // => true
```

### date.**toString**() : string
### date.**toString**(_options_?: object) : string

**Parameters:**

- `options` (optional object): An object with properties influencing the formatting.
The following options are recognized:
- `calendar` (string): Whether to show the calendar annotation in the return value.
Valid values are `'auto'`, `'always'`, and `'never'`.
The default is `'auto'`.

**Returns:** a string in the ISO 8601 date format representing `date`.

This method overrides the `Object.prototype.toString()` method and provides a convenient, unambiguous string representation of `date`.
The string can be passed to `Temporal.Date.from()` to create a new `Temporal.Date` object.

Normally, a calendar annotation is shown when `date`'s calendar is not the ISO 8601 calendar.
By setting the `calendar` option to `'always'` or `'never'` this can be overridden to always or never show the annotation, respectively.
For more information on the calendar annotation, see [ISO string extensions](./iso-string-ext.md#calendar-systems).

Example usage:

```js
Expand Down
7 changes: 7 additions & 0 deletions docs/datetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,9 @@ dt1.equals(dt1); // => true

- `options` (optional object): An object with properties representing options for the operation.
The following options are recognized:
- `calendar` (string): Whether to show the calendar annotation in the return value.
Valid values are `'auto'`, `'always'`, and `'never'`.
The default is `'auto'`.
- `fractionalSecondDigits` (number or string): How many digits to print after the decimal point in the output string.
Valid values are `'auto'`, 0, 1, 2, 3, 4, 5, 6, 7, 8, or 9.
The default is `'auto'`.
Expand All @@ -720,6 +723,10 @@ If no options are given, the default is `fractionalSecondDigits: 'auto'`, which
The value is truncated to fit the requested precision, unless a different rounding mode is given with the `roundingMode` option, as in `Temporal.DateTime.round()`.
Note that rounding may change the value of other units as well.

Normally, a calendar annotation is shown when `datetime`'s calendar is not the ISO 8601 calendar.
By setting the `calendar` option to `'always'` or `'never'` this can be overridden to always or never show the annotation, respectively.
For more information on the calendar annotation, see [ISO string extensions](./iso-string-ext.md#calendar-systems).

Example usage:

```js
Expand Down
12 changes: 12 additions & 0 deletions docs/monthday.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,23 @@ dt1.equals(dt1) // => true

### monthDay.**toString**() : string

**Parameters:**

- `options` (optional object): An object with properties influencing the formatting.
The following options are recognized:
- `calendar` (string): Whether to show the calendar annotation in the return value.
Valid values are `'auto'`, `'always'`, and `'never'`.
The default is `'auto'`.

**Returns:** a string in the ISO 8601 date format representing `monthDay`.

This method overrides the `Object.prototype.toString()` method and provides a convenient, unambiguous string representation of `monthDay`.
The string can be passed to `Temporal.MonthDay.from()` to create a new `Temporal.MonthDay` object.

Normally, a calendar annotation is shown when `monthDay`'s calendar is not the ISO 8601 calendar.
By setting the `calendar` option to `'always'` or `'never'` this can be overridden to always or never show the annotation, respectively.
For more information on the calendar annotation, see [ISO string extensions](./iso-string-ext.md#calendar-systems).

Example usage:
```js
md = Temporal.MonthDay.from('08-24');
Expand Down
12 changes: 12 additions & 0 deletions docs/yearmonth.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,11 +451,23 @@ ym.equals(ym); // => true

### yearMonth.**toString**() : string

**Parameters:**

- `options` (optional object): An object with properties influencing the formatting.
The following options are recognized:
- `calendar` (string): Whether to show the calendar annotation in the return value.
Valid values are `'auto'`, `'always'`, and `'never'`.
The default is `'auto'`.

**Returns:** a string in the ISO 8601 date format representing `yearMonth`.

This method overrides the `Object.prototype.toString()` method and provides a convenient, unambiguous string representation of `yearMonth`.
The string can be passed to `Temporal.YearMonth.from()` to create a new `Temporal.YearMonth` object.

Normally, a calendar annotation is shown when `yearMonth`'s calendar is not the ISO 8601 calendar.
By setting the `calendar` option to `'always'` or `'never'` this can be overridden to always or never show the annotation, respectively.
For more information on the calendar annotation, see [ISO string extensions](./iso-string-ext.md#calendar-systems).

Example usage:

```js
Expand Down
9 changes: 8 additions & 1 deletion docs/zoneddatetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,9 @@ zdt1.equals(zdt1); // => true
- `options` (optional object): An object with properties influencing the formatting.
The following options are recognized:
- `calendar` (string): Whether to show the calendar annotation in the return value.
Valid values are `'auto'`, `'always'`, and `'never'`.
The default is `'auto'`.
- `fractionalSecondDigits` (number or string): How many digits to print after the decimal point in the output string.
Valid values are `'auto'`, 0, 1, 2, 3, 4, 5, 6, 7, 8, or 9.
The default is `'auto'`.
Expand Down Expand Up @@ -1105,7 +1108,11 @@ If no options are given, the default is `fractionalSecondDigits: 'auto'`, which
The value is truncated to fit the requested precision, unless a different rounding mode is given with the `roundingMode` option, as in `Temporal.DateTime.round()`.
Note that rounding may change the value of other units as well.
The string format output by this method can be parsed by [`java.time.ZonedDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html) as long as the calendar is `iso8601`.
Normally, a calendar annotation is shown when `zonedDateTime`'s calendar is not the ISO 8601 calendar.
By setting the `calendar` option to `'always'` or `'never'` this can be overridden to always or never show the annotation, respectively.
For more information on the calendar annotation, see [ISO string extensions](./iso-string-ext.md#calendar-systems).
The string format output by this method can be parsed by [`java.time.ZonedDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html) as long as the calendar annotation is not output.
For more information on `Temporal`'s extensions to the ISO string format and the progress towards becoming a published standard, see [ISO standard extensions](./iso-string-ext.md).
Example usage:
Expand Down
10 changes: 6 additions & 4 deletions polyfill/lib/date.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import {

const ObjectAssign = Object.assign;

function TemporalDateToString(date) {
function TemporalDateToString(date, showCalendar = 'auto') {
const year = ES.ISOYearString(GetSlot(date, ISO_YEAR));
const month = ES.ISODateTimePartString(GetSlot(date, ISO_MONTH));
const day = ES.ISODateTimePartString(GetSlot(date, ISO_DAY));
const calendar = ES.FormatCalendarAnnotation(GetSlot(date, CALENDAR));
const calendar = ES.FormatCalendarAnnotation(GetSlot(date, CALENDAR), showCalendar);
return `${year}-${month}-${day}${calendar}`;
}

Expand Down Expand Up @@ -290,9 +290,11 @@ export class Date {
}
return ES.CalendarEquals(this, other);
}
toString() {
toString(options = undefined) {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
return TemporalDateToString(this);
options = ES.NormalizeOptionsObject(options);
const showCalendar = ES.ToShowCalendarOption(options);
return TemporalDateToString(this, showCalendar);
}
toJSON() {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
Expand Down
7 changes: 4 additions & 3 deletions polyfill/lib/datetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {

const ObjectAssign = Object.assign;

function DateTimeToString(dateTime, precision, options = undefined) {
function DateTimeToString(dateTime, precision, showCalendar = 'auto', options = undefined) {
let year = GetSlot(dateTime, ISO_YEAR);
let month = GetSlot(dateTime, ISO_MONTH);
let day = GetSlot(dateTime, ISO_DAY);
Expand Down Expand Up @@ -59,7 +59,7 @@ function DateTimeToString(dateTime, precision, options = undefined) {
hour = ES.ISODateTimePartString(hour);
minute = ES.ISODateTimePartString(minute);
const seconds = ES.FormatSecondsStringPart(second, millisecond, microsecond, nanosecond, precision);
const calendar = ES.FormatCalendarAnnotation(GetSlot(dateTime, CALENDAR));
const calendar = ES.FormatCalendarAnnotation(GetSlot(dateTime, CALENDAR), showCalendar);
return `${year}-${month}-${day}T${hour}:${minute}${seconds}${calendar}`;
}

Expand Down Expand Up @@ -699,8 +699,9 @@ export class DateTime {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
options = ES.NormalizeOptionsObject(options);
const { precision, unit, increment } = ES.ToSecondsStringPrecision(options);
const showCalendar = ES.ToShowCalendarOption(options);
const roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
return DateTimeToString(this, precision, { unit, increment, roundingMode });
return DateTimeToString(this, precision, showCalendar, { unit, increment, roundingMode });
}
toJSON() {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
Expand Down
8 changes: 6 additions & 2 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,10 @@ export const ES = ObjectAssign({}, ES2020, {
}
return result;
},
FormatCalendarAnnotation: (calendar) => {
FormatCalendarAnnotation: (calendar, showCalendar) => {
if (showCalendar === 'never') return '';
const id = ES.CalendarToString(calendar);
if (id === 'iso8601') return '';
if (showCalendar === 'auto' && id === 'iso8601') return '';
return `[c=${id}]`;
},
ParseISODateTime: (isoString, { zoneRequired }) => {
Expand Down Expand Up @@ -468,6 +469,9 @@ export const ES = ObjectAssign({}, ES2020, {
ToTemporalOffset: (options, fallback) => {
return ES.GetOption(options, 'offset', ['prefer', 'use', 'ignore', 'reject'], fallback);
},
ToShowCalendarOption: (options) => {
return ES.GetOption(options, 'calendar', ['auto', 'always', 'never'], 'auto');
},
ToTemporalRoundingIncrement: (options, dividend, inclusive) => {
let maximum = Infinity;
if (dividend !== undefined) maximum = dividend;
Expand Down
27 changes: 20 additions & 7 deletions polyfill/lib/monthday.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@ import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
import { ISO_MONTH, ISO_DAY, ISO_YEAR, CALENDAR, MONTH_DAY_BRAND, CreateSlots, GetSlot, SetSlot } from './slots.mjs';
import {
ISO_MONTH,
ISO_DAY,
ISO_YEAR,
CALENDAR,
CALENDAR_ID,
MONTH_DAY_BRAND,
CreateSlots,
GetSlot,
SetSlot
} from './slots.mjs';

const ObjectAssign = Object.assign;

function MonthDayToString(monthDay) {
function MonthDayToString(monthDay, showCalendar = 'auto') {
const month = ES.ISODateTimePartString(GetSlot(monthDay, ISO_MONTH));
const day = ES.ISODateTimePartString(GetSlot(monthDay, ISO_DAY));
let resultString = `${month}-${day}`;
const calendar = ES.FormatCalendarAnnotation(GetSlot(monthDay, CALENDAR));
if (calendar) {
const calendar = GetSlot(monthDay, CALENDAR);
const calendarString = ES.FormatCalendarAnnotation(GetSlot(monthDay, CALENDAR), showCalendar);
if (calendarString || !ES.IsTemporalCalendar(calendar) || GetSlot(calendar, CALENDAR_ID) !== 'iso8601') {
const year = ES.ISOYearString(GetSlot(monthDay, ISO_YEAR));
resultString = `${year}-${resultString}${calendar}`;
resultString = `${year}-${resultString}${calendarString}`;
}
return resultString;
}
Expand Down Expand Up @@ -87,9 +98,11 @@ export class MonthDay {
}
return ES.CalendarEquals(this, other);
}
toString() {
toString(options = undefined) {
if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver');
return MonthDayToString(this);
options = ES.NormalizeOptionsObject(options);
const showCalendar = ES.ToShowCalendarOption(options);
return MonthDayToString(this, showCalendar);
}
toJSON() {
if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver');
Expand Down
27 changes: 20 additions & 7 deletions polyfill/lib/yearmonth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@ import { GetISO8601Calendar } from './calendar.mjs';
import { ES } from './ecmascript.mjs';
import { DateTimeFormat } from './intl.mjs';
import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs';
import { ISO_YEAR, ISO_MONTH, ISO_DAY, YEAR_MONTH_BRAND, CALENDAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs';
import {
ISO_YEAR,
ISO_MONTH,
ISO_DAY,
YEAR_MONTH_BRAND,
CALENDAR,
CALENDAR_ID,
CreateSlots,
GetSlot,
SetSlot
} from './slots.mjs';

const ObjectAssign = Object.assign;

function YearMonthToString(yearMonth) {
function YearMonthToString(yearMonth, showCalendar = 'auto') {
const year = ES.ISOYearString(GetSlot(yearMonth, ISO_YEAR));
const month = ES.ISODateTimePartString(GetSlot(yearMonth, ISO_MONTH));
let resultString = `${year}-${month}`;
const calendar = ES.FormatCalendarAnnotation(GetSlot(yearMonth, CALENDAR));
if (calendar) {
const calendar = GetSlot(yearMonth, CALENDAR);
const calendarString = ES.FormatCalendarAnnotation(calendar, showCalendar);
if (calendarString || !ES.IsTemporalCalendar(calendar) || GetSlot(calendar, CALENDAR_ID) !== 'iso8601') {
const day = ES.ISODateTimePartString(GetSlot(yearMonth, ISO_DAY));
resultString = `${resultString}-${day}${calendar}`;
resultString = `${resultString}-${day}${calendarString}`;
}
return resultString;
}
Expand Down Expand Up @@ -288,9 +299,11 @@ export class YearMonth {
}
return ES.CalendarEquals(this, other);
}
toString() {
toString(options = undefined) {
if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver');
return YearMonthToString(this);
options = ES.NormalizeOptionsObject(options);
const showCalendar = ES.ToShowCalendarOption(options);
return YearMonthToString(this, showCalendar);
}
toJSON() {
if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver');
Expand Down
7 changes: 4 additions & 3 deletions polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ export class ZonedDateTime {
options = ES.NormalizeOptionsObject(options);
const { precision, unit, increment } = ES.ToSecondsStringPrecision(options);
const roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
return zonedDateTimeToString(this, precision, { unit, increment, roundingMode });
const showCalendar = ES.ToShowCalendarOption(options);
return zonedDateTimeToString(this, precision, showCalendar, { unit, increment, roundingMode });
}
toLocaleString(locales = undefined, options = undefined) {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
Expand Down Expand Up @@ -416,7 +417,7 @@ function dateTime(zdt) {
return ES.GetTemporalDateTimeFor(GetSlot(zdt, TIME_ZONE), GetSlot(zdt, INSTANT), GetSlot(zdt, CALENDAR));
}

function zonedDateTimeToString(zdt, precision, options = undefined) {
function zonedDateTimeToString(zdt, precision, showCalendar = 'auto', options = undefined) {
const dt = dateTime(zdt);
let year = GetSlot(dt, ISO_YEAR);
let month = GetSlot(dt, ISO_MONTH);
Expand Down Expand Up @@ -455,6 +456,6 @@ function zonedDateTimeToString(zdt, precision, options = undefined) {
const tz = GetSlot(zdt, TIME_ZONE);
const offset = ES.GetOffsetStringFor(tz, GetSlot(zdt, INSTANT));
const zone = ES.TimeZoneToString(tz);
const calendar = ES.FormatCalendarAnnotation(GetSlot(zdt, CALENDAR));
const calendar = ES.FormatCalendarAnnotation(GetSlot(zdt, CALENDAR), showCalendar);
return `${year}-${month}-${day}T${hour}:${minute}${seconds}${offset}[${zone}]${calendar}`;
}
20 changes: 20 additions & 0 deletions polyfill/test/date.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,26 @@ describe('Date', () => {
it('new Date(1914, 2, 23).toString()', () => {
equal(new Date(1914, 2, 23).toString(), '1914-02-23');
});
const d = new Date(1976, 11, 18);
it('shows only non-ISO calendar if calendar = auto', () => {
equal(d.toString({ calendar: 'auto' }), '1976-11-18');
equal(d.withCalendar('gregory').toString({ calendar: 'auto' }), '1976-11-18[c=gregory]');
});
it('shows ISO calendar if calendar = always', () => {
equal(d.toString({ calendar: 'always' }), '1976-11-18[c=iso8601]');
});
it('omits non-ISO calendar if calendar = never', () => {
equal(d.withCalendar('gregory').toString({ calendar: 'never' }), '1976-11-18');
});
it('default is calendar = auto', () => {
equal(d.toString(), '1976-11-18');
equal(d.withCalendar('gregory').toString(), '1976-11-18[c=gregory]');
});
it('throws on invalid calendar', () => {
['ALWAYS', 'sometimes', false, 3, null].forEach((calendar) => {
throws(() => d.toString({ calendar }), RangeError);
});
});
});
describe('Date.from() works', () => {
it('Date.from("1976-11-18")', () => {
Expand Down
19 changes: 19 additions & 0 deletions polyfill/test/datetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,25 @@ describe('DateTime', () => {
equal(dt2.toString(), '1976-11-18T15:23:30');
equal(dt3.toString(), '1976-11-18T15:23:30.1234');
});
it('shows only non-ISO calendar if calendar = auto', () => {
equal(dt1.toString({ calendar: 'auto' }), '1976-11-18T15:23:00');
equal(dt1.withCalendar('gregory').toString({ calendar: 'auto' }), '1976-11-18T15:23:00[c=gregory]');
});
it('shows ISO calendar if calendar = always', () => {
equal(dt1.toString({ calendar: 'always' }), '1976-11-18T15:23:00[c=iso8601]');
});
it('omits non-ISO calendar if calendar = never', () => {
equal(dt1.withCalendar('gregory').toString({ calendar: 'never' }), '1976-11-18T15:23:00');
});
it('default is calendar = auto', () => {
equal(dt1.toString(), '1976-11-18T15:23:00');
equal(dt1.withCalendar('gregory').toString(), '1976-11-18T15:23:00[c=gregory]');
});
it('throws on invalid calendar', () => {
['ALWAYS', 'sometimes', false, 3, null].forEach((calendar) => {
throws(() => dt1.toString({ calendar }), RangeError);
});
});
it('truncates to minute', () => {
[dt1, dt2, dt3].forEach((dt) => equal(dt.toString({ smallestUnit: 'minute' }), '1976-11-18T15:23'));
});
Expand Down
Loading

0 comments on commit 19a9248

Please sign in to comment.