Various parts of JS call these operations implicitly, when converting to a Number or String. Enabling such implicit conversions doesn't mesh well with the strong typing of this proposal. I am wondering, would it make sense to expose this functionality under other names, which don't enable coercion, such as toISOString and nanosecondsSinceEpoch? It would make some code more wordy, but the upside would be less risk of errors.
Ok.
Questions:
toString() isn't actually equivalent to toISOString() because the output is a guaranteed subset of ISO. Is there a better name? Especially since the reverse fromString() should really not be named fromISOString() since that implies submitting any ISO-String would be accepted and we don't want to do that.
valueOf() is designed to produce a BigInt right now, so that's a pro for your comment. However? What should the valueof() then be?
Also I really dislike having both nanoseconds and nanosecondsSinceEpoch the (me personally, I think the nanoseconds value should result in the full nanoseconds since epoch rather than just the nanosecond part). If that's the case, then I'd be good with removing valueOf().
toString is also frequently used for debugging - not just for implicit coercions.
I would not be comfortable with repeating the (necessary and understandable yet still) highly unergonomic situation of Symbol’s undebuggability, nor with “[object Object]”.
@ljharb Could you give more information on what you would like to see from debugging Temporal objects? (Presumably the alternative is, e.g., [object Instant], etc)
An ISO timestamp would be sufficient, although also including the type would be excellent.
@ljharb I mean, what sort of debugging flows are you thinking about? Overall, toString isn't great for reliably serializing an object for debugging--look at its weird output for Arrays ([1].toString() === "1"), which we carried forward for TypedArrays. For this reason, lots of development environments support other ways of displaying objects (e.g., in DevTools consoles).
@pipobscure I don't have a strong preference on either of those questions; the names I was suggesting were just an early draft suggestion. What I'm trying to get at is, if you use those special names, special things will happen, and I'm not sure if that's what we want.
True, things like https://npmjs.com/object-inspect combine the toString output with type output; but it’d be nice to not have to jump through hoops.
@ljharb Is there anything you could tell me about your debugging flow that creates this requirement?
I’m not sure what explanation you’re looking for - i have a value (in a debugger or a logging statement or an exception message or a test framework assertion), instanceof isn’t reliable/cross realm, and i want the quickest and most generic path to a useful string that describes the value.
Almost every platform you can use JS on does deeper introspection of a value than just toString(). Many new ES object types including WeakMap, WeakSet, Map, Set, Promise etc expose no useful toString() or valueOf() value, but play just fine when debugging via console.log in Browsers and Node. If your testing framework does not do the same, then perhaps that is where this should be fixed.
I'm personally very much for dropping toString() and valueOf() here.
I don't have a strong opinion on .toString(), but with some thought .valueOf() being the string subsets of ISO we have been discussing makes (to me) a lot of sense. Because the string subsets are unique on a per-type basis, comparison and sorting functions will work pretty much exactly as expected if we use the string values.
valueOf is actually the more problematic one. It means that comparison doesn't just work within a type but between types. Is this what you really want?
I suppose equality will work, but comparison is still broken.
What do you mean? If we remove valueOf, the meaning of < goes from comparing ns to throwing a TypeError, right? Then, we would add comparison methods.
I would actually vote for removing valueOf() entrirely. It makes things unclear because:
let a = { valueOf: function() { return 3; } };
let b = { valueOf: function() { return 4; } };
b == 4; // true
b.valueOf(); // 4
a < 4; // true
a < b; // false
That to me make valueOf() one of the bad parts™️. This holds especially true since typeof instant.valueOf() === 'bigint'.
In #76 I changed
.toString() to give [object Type ISO-String] - which is good for debugging from log-files where they are stringified..asString() to give ISO-String - (namecheck) to be what .toString() used to be.valueOf() became a .value getter that returns a BigIntComments Please!!!
-1 for toString giving somethign of the form [object Type X]. That is unprecedented in JS and not a good pattern in general. I think it should just do what it does for every other type, and give a useful string representation, e.g. _ISO-String_.
How about
CivilDate.prototype.[to/from]ISOCalendarDateString => YYYY-MM-DD => 1981-04-05CivilDate.prototype.[to/from]ISOWeekDateString => YYYY-Www-D => 2009-W01-1CivilDate.prototype.[to/from]ISOOrdinalDateString => YYYY-DDD => 1981-095"calendar date", "ordinal date", "week date" are concepts defined by the specification.
CivilTimeCivilTime.prototype.[to/from]ISOTimeString => hh:mm:ss.mmmmmmCivilDateTimeCivilDateTime.prototype.[to/from]ISODateTimeString => YYYY-MM-DDThh:mm:ss.mmmmmmIt's wordy though.
@ljqx the reason I dislike the toISO* method names is because there is a suggestion of being ISO complete. This comes back to bite us when we define fromString() because rather than having a full fledged parser that allows all ISO (and other) string configurations, we went with this one because we defined it with reference to toString() and said that all i can parse is the format produced by that.
Of course we could go whole hog on the pairs:
toISOCalendarString() -> fromISOCalendarString()toISOWeekDateString() - fromISOWeekDayString()toISOOrdinalString() - fromISOOrdinalString()toISOTimeString() - fromISOTimeString()toISODateTimeString() - fromISODateTimeString()toISOWeekDateTimeString() - `fromISOWeekDateTimeString()toISOOrdinalTimeString() - fromISOOrdinalTimeString()The question is whether there is any real benefit; also would string formatting not be better left to something other such as Intl.DateTimeFormat and the like.
The conclusion I draw from the discussion so far is:
toString() is a good thing and should output a plain ISO-StringvalueOf() is a bad thing and should not exists on temporal objects.value can be the member with the nanosecond-since-epoch as a BigInt.toJSON() should act like Date and output the ISO-String(Please up/down - vote to indicate whether my summary is accurate or if it's premature)
valueOf()is a bad thing and should not exists on temporal objects
There can be a pretty big difference between valueOf absent vs. identity vs. throwing. Maybe this should be considered in terms of effects upon relational comparison and equality comparison between temporal values and strings/Numbers/BigInts/other temporal values (keeping in mind that @@toPrimitive can preempt its invocation).
Use of relational operators to compare at least date-times with each other and dates with each other might be nice enough to justify non-abrupt completions from cross-type comparisons (even though those results would be meaningless), which in concrete terms would probably look like ToPrimitive(CivilDateTime) and ToPrimitive(CivilDate) returning BigInt (implicitly also supporting both relative and equality comparison against numeric values) and ToPrimitive(CivilTime) throwing.
+1 to https://github.com/tc39/proposal-temporal/issues/74#issuecomment-410205642 re valueOf and toString, except:
value is probably worth another round of bikeshedding (shouldn't this name somehow say what it represents?)I agree regarding .value although I sincerely hate loooong names like nanosecondsSinceUnixEpochWithoutLeapSeconds
Regarding JSON not round-tripping, I know of no way to make this roundtrip without changing JSON which is a no-go to me. And this behaviour is much better than throwing. I consider the String behaviour better than throwing like BigInt, because you only need to deal with reviving if the value is what you are actually interested in. If you are interested in some other part of the tree, you can parse/modify/stringify without having to know about the temporal thing in there. So in terms of ergonomics I think it’s a winner.
On 4 Aug 2018, at 22:46, Daniel Ehrenberg notifications@github.com wrote:
+1 to #74 (comment) re valueOf and toString, except:
value is probably worth another round of bikeshedding (shouldn't this name somehow say what it represents?)
I am not sure if we want to add another toJSON method by default which doesn't round-trip; I am not sure if Date is a model we want to repeat
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
Well, I think we should come to a common conclusion among Temporal and BigInt re the throwing issue. BigInt could also serialize 123n as "123" and require using the BigInt constructor to revive it. However, doing this properly requires understanding the schema. There's an active thread on this question at https://github.com/tc39/proposal-bigint/issues/162 . Last time this was discussed, re Map and Set, the conclusion was to continue to not give useful output; see https://github.com/tc39/proposal-bigint/issues/162#issuecomment-409555519
throwing could break loggers that rely on JSON.stringify/toString (which until now, have assumed vanilla objects/builtins/primitives won't throw), like this real-world example:
/*
* json-logging example from
* https://github.com/kaizhu256/node-utility2/blob/2018.6.21/lib.utility2.js#L4784
*/
// debug middlewareForwardProxy
console.error('serverLog - ' + JSON.stringify({
time: new Date(options.timeStart).toISOString(),
type: 'middlewareForwardProxyResponse',
method: options.method,
url: options.url,
statusCode: response.statusCode | 0,
timeElapsed: Date.now() - options.timeStart,
// extra
headers: options.headers
}));
I raised the possibility of throwing from valueOf, not from toString or toJSON. The effects of which would be similar to Symbols, for which Abstract Relational Comparison also throws (albeit a little later, at the application of ToNumber rather than ToPrimitive).
Summary:
.toString() - generates plain ISO-StringsJSON.stringify() - acts like Date (uses .toString()).value or .valueOf()Can we take this as sort-of agreed? (I'm about to start updating things in advance of the TC39 meeting to reflect the current state)
This is agreed. Going to note this when I present.
JSON.stringify() - acts like Date (uses .toString())
This is the part where I don't really agree--I'd prefer we throw an exception or generate { }. Date is not a precedent to be followed by default, as it fails to round-trip.
{} doesn't round-trip either.
@ljharb I'm not sure what you mean, or how that relates to what we should do here.
You said "I'd prefer we throw an exception or generate { }", and that you didn't want to follow Date because it does not round trip, but "generate { }" does not round trip either.
In other words, if round tripping is a requirement, then throwing an exception is the only option. However, I don't think there's consensus that round tripping is a requirement.
I see, well, I'd say it's better to either fail somewhat loudly/thorougly or be really correct; muddling through makes me a bit worried. In particular, about JSON, we discussed this issue with respect to Map and Set, and came to the conclusion that they should continue to return { }, so I'd like to understand what's different here than in that case. (Maybe they are the same and we should actually revisit the Map/Set proposal.)
The difference is that Map and Set are collections. So there could be arbitrarily nested stuff inside so that there is little to no chance of reconstituting the collection.
On the contrary, the temporal objects all constiture single discreet values, and the objects come with a static function (fromString) that can be used to reconstitute them using the established reviver hook.
In addition, the readable ISO string format actually has data value and works transparently, making multiple round-trips painless.
In my opinion this is the one thing that Date actually got right.
agree with @pipobscure. non-nested data-structures should follow Date's JSON behavior for ease-of-integration with JSON and consistency.
i'm skeptical general-purpose tooling to auto-revive JSON data-structures without application-context will ever be viable. its naive thinking. javascript product development will always require user-intervention in the JSON.parse/revival step. the best tradeoff is too accept that and focus at least on making JSON.stringify automatic.
OK, I take it you all disagree with the decision of BigInt to not use toString as its toJSON, then?
@littledan most definitely. It makes working with BigInt needlessly hard.
All it did is require me to have a mini library:
export const replacer = (k,v)=>('bigint'===typeof v)?(v+'n'):v;
export const reviver = (k,v)=>/^\d+n$/.test(v)?BigInt(v.slice(0,-1)):v;
used like
JSON.parse(JSON.stringify({ a: 123n }, replacer),reviver)
just making it needlessly painful for no benefit. It's not like throwing on JSON.stringify made anything work better.
OK, in this case, I think we should come up with a common decision for both. We were pretty explicit about this decision for BigInt, but it could be revisited without (probably) breaking compatibility.
obsolete. Conclusion: no valueOf but yes on toString
Most helpful comment
The conclusion I draw from the discussion so far is:
toString()is a good thing and should output a plain ISO-StringvalueOf()is a bad thing and should not exists on temporal objects.valuecan be the member with the nanosecond-since-epoch as a BigInt.toJSON()should act likeDateand output the ISO-String(Please up/down - vote to indicate whether my summary is accurate or if it's premature)