18

I need a regular expression to validate durations in the ISO 8601 duration format (with the exception of fractional parts which I don't need).

PnYnMnDTnHnMnS

PnW

Here is what I have:

^P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$

The only problem is that the strings P and PT are allowed with this regex as all of the parts are "zero or one" ?.

  • There needs to be at least one component (date or time)
  • If there is a T then there needs to be a time component (H, M, or S)
  • If there is a T then there may or may not be any date components (Y, M, or D)
  • Overflow is allowed (e.g. P72H is mostly equivalent to P3D)

Acceptable inputs:

P1Y        // date component only
P2MT30M    // date and time components
PT6H       // time component only
P5W        // another date component

Unacceptable inputs:

P         // no components
PT        // no components
P3MT      // T specified but not time components

Right now the invalid strings are passing client-side validation but failing on the server-side because it's passed into DateInteval but I'd like to fail on the client side if possible. If everyone was using Chrome 40+ I could specify minlength='3' on the input element to help but that isn't the case unfortunately.

0

7 Answers 7

26

If you have almost all parts optional, but you want to make sure there is something else after P or T, you can make use of look-aheads:

^P(?=\d+[YMWD])(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d+[HMS])(\d+H)?(\d+M)?(\d+S)?)?$
  ^^^^^^^^^^^^                               ^^^^^^^^^^^^

They require a sequence of digits followed by a letter from the specified set to appear right after the preceding pattern.

See demo

UPDATE

If P can be "empty", use

^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d+[HMS])(\d+H)?(\d+M)?(\d+S)?)?$

See another demo. Here, (?!$) makes sure the string is not equal to P, and there must be some other symbols on the right.

Or, as @UlugbekUmirov suggests, it is enough to just use T(?=\d) (since all the optional parts start with a digit):

^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$

UPDATE 2

If the numbers can be both float or integers, add (?:\.\d+)? after each \d+. Here is an updated pattern from Update 1:

^P(?!$)(\d+(?:\.\d+)?Y)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?W)?(\d+(?:\.\d+)?D)?(T(?=\d)(\d+(?:\.\d+)?H)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?S)?)?$
Sign up to request clarification or add additional context in comments.

Comments

4

The answers above don't include the situation of a decimal fraction (see here for details). The decimal fraction can occur on the last element. The following regex includes decimal fraction:

^P(?!$)((\d+Y)|(\d+\.\d+Y$))?((\d+M)|(\d+\.\d+M$))?((\d+W)|(\d+\.\d+W$))?((\d+D)|(\d+\.\d+D$))?(T(?=\d)((\d+H)|(\d+\.\d+H$))?((\d+M)|(\d+\.\d+M$))?(\d+(\.\d+)?S)?)??$

See here for tests.

Comments

3

According to this message about ISO 8601-2:2019(en), a negative duration is expressed with a minus - sign before the P and it also seems that an explicit positive duration can be indicated with a plus + sign.

Useful to include [-+]? or (-|\+)? before P

Finally, That message also shows the following examples from ISO 8601-2:2019(en) indicating that each component of the duration can be negatively signed.

| EXAMPLE 7 '-P2M1D' is equivalent to 'P-2M-1D'.
| EXAMPLE 8 '-P5DT10H' is equivalent to 'P-5DT-10H'.

Adding [-+]? before digits in components and allowing -+ in the lookahead for T makes sense to be 8601-2 compliant

^[-+]?P(?!$)(([-+]?\d+Y)|([-+]?\d+\.\d+Y$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?(([-+]?\d+W)|([-+]?\d+\.\d+W$))?(([-+]?\d+D)|([-+]?\d+\.\d+D$))?(T(?=[\d+-])(([-+]?\d+H)|([-+]?\d+\.\d+H$))?(([-+]?\d+M)|([-+]?\d+\.\d+M$))?([-+]?\d+(\.\d+)?S)?)??$

Comments

0

Answers above require additional post-processing. /^(-?)P(?=\d|T\d)(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)([DW]))?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/ An output:

["P2Y9M3DT12H31M8.001S", "", "2", "9", "3", "D", "12", "31", "8.001", index: 0, input: "P2Y9M3DT12H31M8.001S", groups: undefined]

1 Comment

supports fractional part in seconds only
0

If you are searching for a regular expression containing a start date and duration, check this out:

^(\d{4}(-\d{2}(-\d{2})?(?!:))?(T\d{2}(:\d{2}(:\d{2})?(\.\d+)?)?)?(Z|([+,-]\d{2}(:\d{2})?))?)?P(([0-9]+([.,][0-9]*)?Y)?([0-9]+([.,][0-9]*)?M)?([0-9]+([.,][0-9]*)?D)?T?([0-9]+([.,][0-9]*)?H)?([0-9]+([.,][0-9]*)?M)?([0-9]+([.,][0-9]*)?S)?)|\d{4}-?(0[1-9]|11|12)-?(?:[0-2]\d|30|31)T((?:[0-1][0-9]|[2][0-3]):?(?:[0-5][0-9]):?(?:[0-5][0-9]|60)|2400|24:00)$

Comments

0

The answers above do not consider this:

  • That according to the standard [according to the standard in page 23][1], only the smallest component can have a decimal fraction.
  • Weeks are only allowed by themselves, and not mixed with any of the other values.
  • The standard admits both commas (,) and full stop (.) to express the decimal part.

This expression takes all of it into account:

^P(?!$)(?:(?:((?:\d+Y)|(?:\d+(?:\.|,)\d+Y$))?((?:\d+M)|(?:\d+(?:\.|,)\d+M$))?((?:\d+D)|(?:\d+(?:\.|,)\d+D$))?(T((?:\d+H)|(?:\d+(?:\.|,)\d+H$))?((?:\d+M)|(?:\d+(?:\.|,)\d+M$))?((?:\d+S)|(?:\d+(?:\.|,)\d+S$))?)?)|(?:\d+(?:(?:\.|,)\d+)?W))$

This expression does not take into account any other extensions to the ISO8601 standard, as the original question asked for, however it should be trivial to add them. [1]: https://web.archive.org/web/20210312212543/http://www.loc.gov/standards/datetime/iso-tc154-wg5_n0038_iso_wd_8601-1_2016-02-16.pdf

Comments

0

You could use Zod for it:

import { z } from "zod";
const DurationSchema = z.string().duration()
DurationSchema.parse("P2W")

More information: https://zod.dev/?id=strings

1 Comment

Please edit this to explain how it answers the question. I don't see anything that takes the input required by the question and I don't see any regex as wanted by the question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.