Regular Expression for date and time(DD/MM/YYYY hh:mm:ss) in QML
In QML2 I didn't find any Calender
control and I have implemented a control which takes date and time as input and I am using the regular expression for the validation which matches dates including leap year and other validations.
The main problem is space/backspace should also be considered as a valid for example:
\s\s/\s\s/\s\s \s\s:\s\s:\s\s
Following is the code :
TextField{
id:textEditDate
width:parent.width * 0.50
height:parent.height
text : "01/01/2017 00:00:00"
inputMask: "99/99/9999 99:99:99"
validator: RegExpValidator { regExp: /^(((([0\s][1-9\s]|[1\s][0-9\s]|[2\s][0-8\s])[\/]([0\s][1-9\s]|[1\s][012\s]))|((29|30|31)[\/]([0\s][13578\s]|[1\s][02\s]))|((29|30)[\/]([0\s][4,6,9]|11)))[\/]([19\s[2-9\s][0-9\s])\d\d|(^29[\/]02[\/]([19\s]|[2-9\s][0-9\s])(00|04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96)))\s([0-1\s]?[0-9\s]|2[0-3\s]):([0-5\s][0-9\s]):([0-5\s][0-9\s])$/}
horizontalAlignment: Text.AlignHCenter
inputMethodHints: Qt.ImhDigitsOnly
}
Now, everything works well except for the year and I am not able to match backspace/space for the year and user is not able to clear the year.
Can you please suggest how to achieve this ? or is there any other method to do this.
Answer
Brief
So I decided to make a really nice regex that actually works on leap years properly! I then added the rest of the logic you required, and voila, a beauty!
Code
See regex in use here
(?(DEFINE)
(?# Date )
(?# Day ranges )
(?<d_day28>0[1-9]|1\d|2[0-8])
(?<d_day29>0[1-9]|1\d|2\d)
(?<d_day30>0[1-9]|1\d|2\d|30)
(?<d_day31>0[1-9]|1\d|2\d|3[01])
(?# Month specifications )
(?<d_month28>02)
(?<d_month29>02)
(?<d_month30>0[469]|11)
(?<d_month31>0[13578]|1[02])
(?# Year specifications )
(?<d_year>\d+)
(?<d_yearLeap>(?:\d*?(?:(?:(?!00)[02468][048]|[13579][26])|(?:(?:[02468][048]|[13579][26])00))|[48]00|[48])(?=\D))
(?# Valid date formats )
(?<d_format>
(?&d_day28)\/(?&d_month28)\/(?&d_year)|
(?&d_day29)\/(?&d_month29)\/(?&d_yearLeap)|
(?&d_day30)\/(?&d_month30)\/(?&d_year)|
(?&d_day31)\/(?&d_month31)\/(?&d_year)
)
(?# Time )
(?# Time properties )
(?<t_period12>(?i)[ap]m|[ap]\.m\.(?-i))
(?# Hours )
(?<t_hours12>0\d|1[01])
(?<t_hours24>[01]\d|2[0-3])
(?# Minutes )
(?<t_minutes>[0-5]\d)
(?# Seconds )
(?<t_seconds>[0-5]\d)
(?# Milliseconds )
(?<t_milliseconds>\d{3})
(?# Valid time formats )
(?<t_format>
(?&t_hours12):(?&t_minutes):(?&t_seconds)(?:\.(?&t_milliseconds))?\ ?(?&t_period12)|
(?&t_hours24):(?&t_minutes):(?&t_seconds)(?:\.(?&t_milliseconds))?
)
(?# Datetime )
(?<dt_format>(?&d_format)\ (?&t_format))
)
\b(?&dt_format)\b
Or in one line...
See regex in use here
\b(?:(?:0[1-9]|1\d|2[0-8])\/(?:02)\/(?:\d+)|(?:0[1-9]|1\d|2\d)\/(?:02)\/(?:(?:\d*?(?:(?:(?!00)[02468][048]|[13579][26])|(?:(?:[02468][048]|[13579][26])00))|[48]00|[48])(?=\D))|(?:0[1-9]|1\d|2\d|30)\/(?:0[469]|11)\/(?:\d+)|(?:0[1-9]|1\d|2\d|3[01])\/(?:0[13578]|1[02])\/(?:\d+))\ (?:(?:0\d|1[01]):(?:[0-5]\d):(?:[0-5]\d)(?:\.(?:\d{3}))?\ ?(?:(?i)[ap]m|[ap]\.m\.(?-i))|(?:[01]\d|2[0-3]):(?:[0-5]\d):(?:[0-5]\d)(?:\.(?:\d{3}))?)\b
Explanation
I'll explain the first version as the second version is simply a slimmed down version of it. Note that the regex can easily be changed to accommodate for more formats (only 1 format with slight variations is accepted, but this is a very customizable regex).
-
d_days28: Match any number from
01
to28
-
d_days29: Match any number from
01
to29
-
d_days30: Match any number from
01
to30
-
d_days31: Match any number from
01
to31
-
d_month28: Match months that may only have 28 days (February - thus
02
) -
d_month29: Match months that may only have 29 days (February - thus
02
) -
d_month30: Match months that only have 30 days (April, June, September, November - thus
04, 06, 09, 11
) -
d_month31: Match months that only have 31 days (January, March, May, July, August, October, December - thus
01, 03, 05, 07, 08, 10, 12
) -
d_year: Match any year (must have at least one digit
\d
) -
d_yearLeap: I'll break this into multiple segments for better clarity
-
\d*?
- Match any number of digits, but as few as possible
- Match one of the following
-
(?:(?:(?!00)[02468][048]|[13579][26])|(?:(?:[02468][048]|[13579][26])00))
- Match one of the following -
(?:(?!00)[02468][048]|[13579][26])
- Match one of the following- One of
02468
, followed by one of048
, but not00
- One of
13579
, followed by one of26
- One of
-
(?:(?:[02468][048]|[13579][26])00)
- Match one of the following, followed by00
- One of
02468
, followed by one of048
- One of
13579
, followed by one of26
- One of
-
[48]00
- Match400
or800
-
[48]
- Match4
or8
-
-
(?=\D|\b)
- Ensure what follows is either a non-digit character\D
or word boundary character\b
-
- d_format: This points to previous groups in order to ensure months are properly formatted and match the days/month and days/year(leap year) requirements so that we can ensure proper date validation
-
t_period: This was added in case others needed this for validation purposes
- Ensures the period is either
am, pm, a.m, p.m
or their respective uppercase versions (including things such asa.M
where multliple cases are used)
- Ensures the period is either
-
t_hours12: Match any hour from
00
to11
-
t_hours24: Match any hour from
00
to23
-
t_minutes: Match any minutes from
00
to59
-
t_seconds: Match any seconds from
00
to59
-
t_milliseconds: Match any 3 digits (
000
to999
) - t_format: This points to previous groups in order to ensure time is properly formatted. I've added an additional time setting (as well as an addition including milliseconds and time period for others' use)
-
dt_format: Datetime format to check against (in your case it's
date time
- separation by a spacecharacter)
- Following the define block is
\b(?&dt_format)\b
, which simply matches thedt_format
as specified above, ensuring what precedes and supercedes it is a word boundary character (or no character)\b
Leap year
To further understand the leap year section of the regex...
I am assuming the following:
- All years are NOT leap years, unless, the following is true
- ((Year modulo
4
is0
) AND (year modulo100
is not0
)) OR (year modulo400
is0
) - Source: leap year calculation
- Leap years have always existed (at least since year 1) - since I don't want to start assuming and do even more research.
- ((Year modulo
The regex works by ensuring:
- All leap years that end in
0, 4, 8
are preceded by a0, 2, 4, 6, 8
(all of which result in0
after modulus -> i.e.24 % 4 = 0
) - All leap years that end in
2, 6
are preceded by a1, 3, 5, 7, 9
(all of which result in0
after modulus -> i.e.32 % 4 = 0
) - All leap years that end in
00
, for 1. and 2., are negated ((?!00)
does this) - All leap years that end in
00
are preceded by 1. and 2. (exactly the same since4 * 100 = 400
- nothing needs to be changed except the last two digits) - Add the years
400, 800, 4, 8
since they are not satisfied by any of the above conditions
Edits
October 25th, 2017
Thanks to @sln for the input on the leap year's functionality. The regex below performs slightly faster due to changes provided in the comments of this answer by sln (on a separate question). Changed (?:(?!00)[02468][048]|[13579][26])
to (?:0[48]|[13579][26]|[2468][048])
in the leap year section.
See regex in use here
(?(DEFINE)
(?# Date )
(?# Day ranges )
(?<d_day28>0[1-9]|1\d|2[0-8])
(?<d_day29>0[1-9]|1\d|2\d)
(?<d_day30>0[1-9]|1\d|2\d|30)
(?<d_day31>0[1-9]|1\d|2\d|3[01])
(?# Month specifications )
(?<d_month28>02)
(?<d_month29>02)
(?<d_month30>0[469]|11)
(?<d_month31>0[13578]|1[02])
(?# Year specifications )
(?<d_year>\d+)
(?<d_yearLeap>(?:\d*?(?:(?:0[48]|[13579][26]|[2468][048])|(?:(?:[02468][048]|[13579][26])00))|[48]00|[48])(?=\D|\b))
(?# Valid date formats )
(?<d_format>
(?&d_day28)\/(?&d_month28)\/(?&d_year)|
(?&d_day29)\/(?&d_month29)\/(?&d_yearLeap)|
(?&d_day30)\/(?&d_month30)\/(?&d_year)|
(?&d_day31)\/(?&d_month31)\/(?&d_year)
)
(?# Time )
(?# Time properties )
(?<t_period12>(?i)[ap]m|[ap]\.m\.(?-i))
(?# Hours )
(?<t_hours12>0\d|1[01])
(?<t_hours24>[01]\d|2[0-3])
(?# Minutes )
(?<t_minutes>[0-5]\d)
(?# Seconds )
(?<t_seconds>[0-5]\d)
(?# Milliseconds )
(?<t_milliseconds>\d{3})
(?# Valid time formats )
(?<t_format>
(?&t_hours12):(?&t_minutes):(?&t_seconds)(?:\.(?&t_milliseconds))?\ ?(?&t_period12)|
(?&t_hours24):(?&t_minutes):(?&t_seconds)(?:\.(?&t_milliseconds))?
)
(?# Datetime )
(?<dt_format>(?&d_format)\ (?&t_format))
)
\b(?&dt_format)\b
Your sequence to match the year is:
([19\s[2-9\s][0-9\s])\d\d
Which looks malformed, as the brackets do not match.
Also, the presence of the two digits (using \d
) means that the expression will not match white space.