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" ?
.
T
then there needs to be a time component (H, M, or S)T
then there may or may not be any date components (Y, M, or D)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.
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)?)?$
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.
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)?)??$
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With