Proposal-temporal: Combined Parsing Convenience Method

Created on 31 Oct 2019  Â·  39Comments  Â·  Source: tc39/proposal-temporal

We should consider a way to parse a string once, and output multiple objects. Like this:

Temporal.Parse('1900-01-01T00:00:00-01:00')

returns an object with properties:

{ 
Absolute: absolute, 
DateTime: datetime, //1900-01-01T00:00:00
Time : Time, // 00:00
Date: Date //1900-01-01 ,
Zone: Zone //fixed zone of negative one
/// and all the other types, or at least all the most common ones
}

This allows the string to only be parsed once, which is critical in some high-performance scenarios.

It might make sense to add an option to the parse parameter to specify which types are returned. Something like:

Temporal.Parse(Temporal.Parse('1900-01-01T00:00:00-01:00'), ['Date', 'DateTime', 'Absolute', 'Zone'])

I kinda hate the string array so ideas welcome there.

@mj1856 for reference.

behavior enhancement

Most helpful comment

It’s simpler, for one. Typically we only use getters on the prototype, and only for accessing data intrinsic to the type of the object. Parse produces a normal object - not an instance.

Having them be getters also raises many more questions. Would these getters be enumerable, so Object.assign and object spread works? Would each object produced have getter functions that are ===, reading internal slots off of the otherwise-normal object, or would new !== getters have to be created for each parse?

All 39 comments

/cc @littledan @pipobscure @Ms2ger

I'm trying to understand the use case here. Is this issue sort of an effect of how we don't have a ZonedInstant type anymore? For an example like the one above, without a timezone, I don't see why we wouldn't use Temporal.DateTime.from[String], and then project the other types from there. If this is about parsing with a timezone, what if it returned an object with just a DateTime and tz string?

s/tz string/a TimeZone?

@littledan sort of. But your solution wouldn’t work, because DateTime + TimeZone is not guaranteed to exist and not guaranteed to be specific if it does. So you’d have to parse an Absolute as well. And then you are at this function.

If you have the Absolute, can you not cheaply derive the others from it?

@ljharb nope, because an Absolute enforces the TimeZone with the given offset. So there are situations where you can parse a TimeZone and a DateTime but not an Absolute. Plus with an Absolute you also loose the TimeZone.

If this is a perf consideration, couldn’t engines silently memoize their internal parsed representations, making repeat froms into different types faster? Does it need a user-exposed mechanism, forcing the user to handle it themselves?

Can we reach consensus on if this needs to be done?

Resolution: Yes

const { absolute, dateTime, timeZone, date, time } = Temporal.Parse(string);

Beautiful :-)

Wait, I wanted to make a comment on the name: can this be lower case, matching all JS methods?

Separately, @sffc proposed making this an object with getters returning the Temporal types, to permit lazy allocation. I didn't hear any concerns raised.

Agreed: it will be lowercased and the return will be an object with readonly properties. (whether that’s getters or just writable:false is an imp detail we should’t care about and can be done to optimise parsing. {it’s easy to convince me otherwise; any rationale will do})

In specifications and implementations that try to be 100% conformant, getter vs read-only is not just an implementation detail. Do you have any concerns about the getter idea?

What does the getter do for missing parts? If it just returns undefined then ok, if it throws that would be bad.

I was imagining it would return undefined.

why would we need getters? the point of this api is to eagerly parse into objects for later usage - why not just normal writable data properties?

The proposal requires instantiating 5 or more objects, when the user might only care for 2 of them, and the other three could be wasted. However, maybe that's more of a theoretical concern. The cost of parsing a date is probably at least an order of magnitude higher than a few superfluous object creations. (I have not done any benchmarks.)

I don't know how to judge the performance issues here. Given that this is a performance-motivated feature in the first place (otherwise you could just call Temporal.<type>.from several types), I'm not sure if we can immediately discard the allocation cost.

I think getters are probably the way to implement in a polyfill. The inly question I have is whether we need to specify this or whether it’s better to leave this to implementers.

It is always necessary to specify whether properties created by built-in functions are accessors or data properties.

Why don't we leave this question open for Stage 3 and get feedback from implementers when they get around to implementing it.

@sffc it's a stage 2 concern, not a stage 3 concern (altho changing it as a result of implementer feedback is fine in stage 3). The proposal has to pick one to be eligible for stage 3, imo.

Ok. In that case let’s go with getters. It makes it easy, and if there is implementer feedback we can change it.

I’d prefer normal data properties, absent compelling evidence to make them getters.

I do agree with Jordan that we need an initial answer here for Stage 3. It would be good to understand the argument for data properties--could you explain the reasoning why you would prefer it?

It’s simpler, for one. Typically we only use getters on the prototype, and only for accessing data intrinsic to the type of the object. Parse produces a normal object - not an instance.

Having them be getters also raises many more questions. Would these getters be enumerable, so Object.assign and object spread works? Would each object produced have getter functions that are ===, reading internal slots off of the otherwise-normal object, or would new !== getters have to be created for each parse?

Typically we only use getters on the prototype,

Yes, agreed. It wasn't written out explicitly here, but the idea would be to have a mini-class for this purpose, just like Intl.Segmenter results, not to use own getters.

and only for accessing data intrinsic to the type of the object.

What do you mean? How would these not be intrinsic to the type of object?

Having them be getters also raises many more questions.

I think we can answer all of these questions to say: they are on the prototype, just like any normal class. I agree that we shouldn't suddenly break with conventions here.

It makes no sense to me to make a “mini class” for this use case (altho i agree it would solve the question posed here).

A plain object with data properties makes sense to me; and adding yet another class/type to a crowded proposal seems like overkill. If it’s not going to be a plain object, i think that “parse” should be deferred to a followup proposal.

I agree that we shouldn't suddenly break with conventions here.

What conventions are those? https://www.ecma-international.org/ecma-262/10.0/index.html doesn't seem to define very many built-in accessor properties:

  1. {RegExp,Array,%TypedArray%,Map,Set,ArrayBuffer,SharedArrayBuffer,Promise}[Symbol.species]
  2. Symbol.prototype.description
  3. RegExp.prototype.{source,flags,dotAll,global,ignoreCase,multiline,sticky,unicode} (with an interesting distinction of lastIndex as a data property on RegExp instances)
  4. %TypedArray%.prototype.{buffer,byteLength,byteOffset,length,[Symbol.toStringTag]} (with an interesting distinction of BYTES_PER_ELEMENT as a data property on each TypedArray prototype object)
  5. Map.prototype.size
  6. Set.prototype.size
  7. ArrayBuffer.prototype.byteLength
  8. SharedArrayBuffer.prototype.byteLength
  9. DataView.prototype.{buffer,byteLength,byteOffset}
  10. Object.prototype.__proto__
  11. %FunctionPrototype%.{caller,arguments}
  12. ordinary arguments.callee
  13. exotic arguments map (not exposed to code)

Setting aside Symbol.species and special legacy-related use, it seems to me that code-accessible built-in accessor properties are for exposing immutable primitive-valued characteristics of directly-constructible instances. In which case they wouldn't make sense here.

@gibson042 @ljharb do we have consensus on this?

If the purpose is ergonomics, then I'd be in favor. But if the goal is performance, I don't know if we can conclude a priori that it will be faster to construct this kind of structure. (Note that we're making lots of other decisions that make it more difficult to implement Temporal with high performance, e.g., Timezone/Calendar subclassing.) Absent some more evidence/data that this kind of use case is an actual performance problem that this sort of API can resolve, I'd lean towards omitting this feature from the initial version of Temporal.

We discussed this on the champions call today.

It seems useful to make a convenience method returning at least { dateTime, timeZone }, since those are the two explicit components of the ISO string. However, it's less clear that we need to also need to include date, time, absolute, etc., since those can be quickly extracted from dateTime and timeZone.

I also wanted to suggest naming this method Temporal.parseISOString to emphasize that this function is about ISO strings, and not any other weird formats like Date.parse supports.

Is there no type that’s a combination of DateTime and TimeZone?

Is there no type that’s a combination of DateTime and TimeZone?

We have DateTime and Absolute, and you use a TimeZone to transition between them. We previously had ZonedDateTime, but that's been gone for a long time. It was removed in #220

Seems like this is the perfect use case for it, rather than an object with two things in it ¯\_(ツ)_/¯

If we leave the spec until later, how close is this to being mergeable? I think it's probably doable for our v1 of the polyfill.

Discussions about re-adding a combined type (ZonedAbsolute) have been reopened in #569.

Meeting, May 28: We won't be shipping this in the initial polyfill but will revisit it for Stage 3 (including a naming concern raised by @justingrant)

Meeting 2020-09-11: We do not plan to ship this parsing method because its use cases are almost entirely covered by LocalDateTime.from.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pipobscure picture pipobscure  Â·  6Comments

littledan picture littledan  Â·  4Comments

AlicanC picture AlicanC  Â·  4Comments

ptomato picture ptomato  Â·  5Comments

thefliik picture thefliik  Â·  3Comments