Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fromISOTime, toISOTime and toMillis to Duration #803

Merged
merged 14 commits into from
Feb 13, 2021
Merged

Add fromISOTime, toISOTime and toMillis to Duration #803

merged 14 commits into from
Feb 13, 2021

Conversation

thomas-darling
Copy link
Contributor

This resolves #625, by adding a fromISOTime and toISOTime method to the Duration class.
The design of the methods is closely aligned with the design of similar, existing methods, such as fromISO and toISO.

Additionally, since the Duration class has a fromMillis method, I've also added a corresponding toMillis method.
The justification for this is, that having corresponding pairs of from/to methods, makes it easy to roundtrip a value in the same format, and that it aligns with the DateTime class, which already has both method.

@jsf-clabot
Copy link

jsf-clabot commented Oct 6, 2020

CLA assistant check
All committers have signed the CLA.

src/duration.js Outdated
format = format.replace(/:/g, "");
}

if (opts.suppressPrefix === "never" || opts.suppressPrefix === "auto" && (format === "hh" || format === "hhmm")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not against options like these, but we need to make sure they're consistent. We don't support these elsewhere, do we?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, DateTime has similar options, so I designed this to behave the same way.
See https://github.com/moment/luxon/blob/master/src/datetime.js#L1534

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically regarding the prefix suppression - this is unique to the time format:

As of ISO 8601-1:2019, the basic format is T[hh][mm][ss] and the extended format is T[hh]:[mm]:[ss]. Earlier versions omitted the T in both formats.
...
ISO 8601-1:2019 allows the T to be omitted in the extended format, as in "13:47:30", but only allows the T to be omitted in the basic format when there is no risk of ambiguity with date expressions.

https://en.wikipedia.org/wiki/ISO_8601#Times

So I made the default behavior here auto, i.e. omit if possible - but if you're interacting with a system that always/never expects the prefix, this option allows you to override that behavior.

@@ -118,6 +118,29 @@ function extractIANAZone(match, cursor) {
return [{}, zone, cursor + 1];
}

// ISO time parsing

const isoTimeOnly = /^T?(\d\d)(?::?(\d\d)(?::?(\d\d)(?:[.,](\d{1,30}))?)?)?$/;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these identical to isoTimeBaseRegex and extractISOTime?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are very similar, but not quite identical.

  • isoTimeOnly includes the optional T prefix, and matches the beginning/end of the string - isoTimeBaseRegex does not.

    This means isoTimeBaseRegex would match a value such as foo00bar, even though that's obviously not a valid Time value.
    But I guess we could consider changing it to this, to reuse the existing regex:

    const isoTimeOnly = RegExp(`^T?${isoTimeBaseRegex.source}$`)
    
  • extractISOTimeOnly preserves the precision of the value, by only setting the values that are actually specified in the string - isoTimeBaseRegex always sets e.g. seconds to 0, even if not specified.

    Either the seconds, or the minutes and seconds, may be omitted from the basic or extended time formats for greater brevity but decreased precision - https://en.wikipedia.org/wiki/ISO_8601#Times

    One could of course argue extractISOTime should be fixed so it too preserves the precision - I'm just afraid that might be a breaking change, as code may have been written to expect seconds in a DateTime instance to always have a value.

    There's also a minor difference in the property names in the returned object - e.g. seconds instead of second. That's because Duration.fromObject only accepts the plural version, which kinda makes sense, as it may represent arbitrary durations, and the singular name only makes sense when talking specifically about a time of day.

@thomas-darling
Copy link
Contributor Author

Hmm, looking at this again, I'd actually like to refactor it a little.
The way I implemented the options here, is not quite as consistent with the rest of the code as I thought it was 🙂

@icambron
Copy link
Member

icambron commented Nov 9, 2020

@thomas-darling ok, let me know when you want me to take another look. I can spend some real time looking at this when it's ready; my comments so far were the result of only a very quick readthrough

@thomas-darling
Copy link
Contributor Author

@icambron, will do 👍
I got a little busy here, but I'm working on it, so should have it ready for review soon 🙂

test("DateTime#toISOTime({suppressSeconds: true}) will suppress milliseconds if they're zero", () => {
expect(dt.set({ second: 0, millisecond: 0 }).toISOTime({ suppressSeconds: true })).toBe("09:23Z");
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed because it is identical to the test above

checkTime("11", { hours: 11, minutes: 0, seconds: 0 });
checkTime("T1122", { hours: 11, minutes: 22, seconds: 0 });
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exactly matches how the time component of a date is parsed - including the, in my opinion, strange behavior where milliseconds are undefined if not specified, while the other values default to 0.

A general discussion could be had about whether defaulting to 0 is the correct behavior, as it means we actually loose the information about the precision of the value. That said, the need for that information is probably an uncommon edge case, and defaulting to 0 is very convenient, as it means users won't need null checks - so I'm ok with that.

However, I do think milliseconds should then also default to 0, both here and when parsing a date, as the current behavior is quite unexpected - especially since the typings for DateTime claim the millisecond property will always be a number...

Thoughts?


return value.toFormat(fmt);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is designed to behave just like the existing toISOTime() in the DateTime class - and that one has been extended to support adding the prefix, but is otherwise unchanged.

In my first implementation, the default was to automatically choose whether to add the prefix, depending on the format. However, I've since decided to drop that, and instead make it an explicit option, defaulting to false.

Technically, the 2019 version of the spec does require the prefix to be present for the basic format hhmm, but the previous version didn't, so automatically adding it now would have been a breaking change.

Also, since the user already has to specify options to get the basic format, they might as well also specify if they want the prefix...

@thomas-darling
Copy link
Contributor Author

thomas-darling commented Nov 11, 2020

@icambron I think this is done now, so feel free to review when you have the time 🙂

(by the way, I think the CI build may be stuck - looks like it's been queued for quite a while now)
Never mind, it works now

@thomas-darling
Copy link
Contributor Author

@icambron, turns out I had a few bugs there - but should be all done now 🙂

@JensUngerer
Copy link

JensUngerer commented Dec 6, 2020

I've justed "toied with the idea of doing" a merge request for a toMillis method in the Duration class... .
I am happy that i found this merge - request a minute ago...

I am using luxon for my TimeTracker project.
It is exactly what i've searched for.
"But", it "lacked" one kind of "functionality": a toMillis method in the Duration class... .

@icambron, turns out I had a few bugs there - but should be all done now 🙂

I am looking forward seeing the merge - request to be merged.

@icambron icambron merged commit 8ec10bb into moment:master Feb 13, 2021
@icambron
Copy link
Member

Sorry for the long delay. Merged and will be released shortly. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Duration.fromHTMLTime()
4 participants