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.
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.
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
to 28
- d_days29: Match any number from
01
to 29
- d_days30: Match any number from
01
to 30
- d_days31: Match any number from
01
to 31
- 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 of 048
, but not 00
- One of
13579
, followed by one of 26
(?:(?:[02468][048]|[13579][26])00)
- Match one of the following, followed by 00
- One of
02468
, followed by one of 048
- One of
13579
, followed by one of 26
[48]00
- Match 400
or 800
[48]
- Match 4
or 8
(?=\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 as a.M
where multliple cases are used)
- t_hours12: Match any hour from
00
to 11
- t_hours24: Match any hour from
00
to 23
- t_minutes: Match any minutes from
00
to 59
- t_seconds: Match any seconds from
00
to 59
- t_milliseconds: Match any 3 digits (
000
to 999
)
- 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 space
character)
- Following the define block is
\b(?&dt_format)\b
, which simply matches the dt_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
is 0
) AND (year modulo 100
is not 0
)) OR (year modulo 400
is 0
)
- 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.
The regex works by ensuring:
- All leap years that end in
0, 4, 8
are preceded by a 0, 2, 4, 6, 8
(all of which result in 0
after modulus -> i.e. 24 % 4 = 0
)
- All leap years that end in
2, 6
are **preceded* by a 1, 3, 5, 7, 9
(all of which result in 0
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 since 4 * 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