Ruby Regular Expression to validate day and month of a date - ruby-on-rails

On rubular I tested and confirmed that this does a good job confirming the desired format of a date entry:
\A\d\d\/\d\d\/\d\d\d\d\z
Tests:
01/02/2000 #pass
11/21/2014 #pass
11-21-2014 #fail
A3-21-2014 #fail
I want to make it a little bit better, and it will be good enough for me. What I want is to confirm that the "month field" (the first two digits) is anywhere from 01 - 12, where each single digit is led by a zero. (Ex: 01,02,03 etc as opposed to: 1,2,3).
Next: I want to do the same thing for the next two digits to confirm that the next two digits (the day field) is between 01 - 31. Same thing: Each single digit needs to lead with a zero.
Tests:
01/02/2017 #pass
12/31/2017 #pass
1/02/2017 #fail
01/2/2017 #fail
50/01/2017 #fail
01/50/2017 #fail
I realize that this regex will be inaccurate for those months that have fewer than 31 days, but it is good enough for what I am doing.

Well this should get you most of the way there:
/((02\/[0-2]\d)|((01|[0][3-9]|[1][0-2])\/(31|30|[0-2]\d)))\/[12]\d{3}/
Granted it does not handle the following:
Leap Years e.g. 02/29 is acceptable regardless of the year
All Years from 1000-2999 are acceptable
Months with only 30 days e.g. 09/31 is acceptable
Small Breakdown in case links break:
Here is the runout on Rubular
Here is an explanation from Regex101
(02\/[0-2]\d) - Starts with 02/ then allow 0-2 followed by 0-9
OR ((01|[0][3-9]|[1][0-2]\/(31|30|[0-2]\d)) - Starts with (01 or 0 followed by 3-9 or 1 followed by 0-2) followed a / followed by 31 or 30 or 0-2 followed by 0-9
In both cases must be followed by 1 or 2 followed by 3 digits 0-9
Really wish ruby supported look behind conditionals like true pcre Example for edification
As a Note: as mentioned in the comments rescuing a parsing failure is probably easier than using a regex but I figured since you asked.

What I did was used the american_date gem. On your date inputs: the user should enter the date in the format of: "mm/dd/yyyy".
In order to force the user to enter the date in this format: I used jquery-inputmask-rails. I defined my mask like so:
$('.mask_american_date').inputmask({mask: "99/99/9999"});
Now there will be a nice mask on the date input that looks like this:
__/__/____
Now: all you need is a presence validator for the date field in your model:
validates_presence_of :date_of_birth, message: "Date is either invalid or is not present".
And this covers everything. How american date works is it takes the user input and attempts to convert it into a date. If it cannot convert the user input into a date for any reason: it will return nil which triggers the above validation.
This includes a bad month entry or a bad day entry. American Date is smart enough to know, for example, that September only has 30 days in it. So: if the user enters "31" for the day section, ex: 09/31/2017, american date will convert the date to nil.

Related

How to get date out of a cell containing the string "2017|03"?

Here is my data:
I am trying to build a SUMIFS formula to sum the sessions, if the month = "last month" (i.e., parsed out of these strings), and the Channel Grouping = "Display".
Here's what I have so far:
=SUMIFS(H3:H,F3:F,________,G3:G,"Direct")
Since this is a string, not a date, I am not sure how to get it to match "last month".
Why not build up a string like this (or just hard-code it?)
=sumifs(H3:H,F3:F,year(today())&"|"&text(month(today())-1,"00"),G3:G,"Direct")
This builds up a string equal to "2017|03" by taking the year from today's date (2017) and one less than the month number from today's date which at time of writing is April so 4-1=3. The text function formats it with a leading zero. So the whole thing is"2017" & "|" & "03" which gives "2017|03" - this is compared against column F.
Note: January would be a special case (existing formula would give "2018|00" for previous month to January 2018 so would need a bit of extra code to cover this case and make it fully automatic).
By 'hard-code it' I mean just put 2017|03 in as a literal string like this
=sumifs(H3:H,F3:F,"2017|03",G3:G,"Direct")
then just change it manually for different months.
Here is a more general formula
=sumifs(H3:H,F3:F,year(eomonth(today(),-1))&"|"&text(month(eomonth(today(),-1)),"00"),G3:G,"Direct")
Just change the -1 to -2 etc. for different numbers of months.
EDIT
In light of #Max Makhrov's answer, this can be shortened significantly to
=sumifs(H3:H,F3:F,text(eomonth(today(),-1),"YYYY|MM"),G3:G,"Direct")
I would like to add two more options:
1
This formula is slightly shorter and more powerrful, because it gives the full control over date format:
=TEXT(TODAY(),"YYYY|MM")
formula syntax is here:
https://support.google.com/docs/answer/3094139?hl=en
2
In your case converting date to string is more efficient because it calculates one time in the formula, so there's fewer calculations. But sometimes you need to convert text into date. In this case I prefer using regular expresions:
=JOIN("/",{REGEXEXTRACT("2017|03","(\d{4})\|(\d{2})"),1})*1
How it works
REGEXEXTRACT("2017|03","(\d{4})\|(\d{2})") gives 2 separate cells output:
2017 03
{..., 1} adds 1 to ... and adds it to the right:
2017 03 1
JOIN("/", ...) joins the ... input:
2017/03/1
This looks like date, but to make it real date, multimpy it by 1:
"2017/03/1"*1 converts string that looks like date into a number 42795 which is serial number for date 2017 march 01

Rails - regex to find specific date

In app I build to learn rails, I am working on finding data. Now I have this case: in this text, I want to find the right delivery date.
"delivery date 01/12/2015 delivery note 30112016 invoice date 03.01.2016"
The regex I made get all the dates:
[0-9]{1,2}[-.\/][0-9]{1,2}[-.\/][0-9]{2,4}
How to add the condition that it picks the date preceded "delivery date"?
Add delivery date to the pattern and capture the date:
s[/delivery date\s*(\d{1,2}[-.\/]\d{1,2}[-.\/]\d{2,4})/, 1]
See the online Ruby demo
The 1 argument tells Ruby to only fetch the contents captured within the first capturing group now.
Just in case you are interested in dates that have consistent separators, you may consider using
/delivery date\s*(\d{1,2}([-.\/])\d{1,2}\2\d{2,4})/
^^^^^^^^ ^^
where the separator is captured into Group 2 and the value is re-used later with the backreference \2.

Validations for excluded dates.

My Code works. The problem I have here is that dates like November 31 and April 31 can occur every year from 2010 down to 1995 which means I have to type in all these dates e.g [Date.new(2012,09,31),Date.new(2011,09,31)] and so on. I am trying to make this validation work for only the month and the day to avoid too much typing or in other words shorten my code please see a description of my
Model/profile.rb
validate :excluded_dates
private
def excluded_dates
exclusion_dates = [Date.new(2012,9,31), Date.new(2012,2,1)]
if exclusion_dates.include?(self.next_shoeing)
self.errors.add(:next_shoeing, "cannot be on a reserved date.")
end
First of all doing Date.new(2012,9,31) will throw you an ArgumentError: invalid date. IMO there is no need to validate this as no one will be able to construct such date (just handling this exception outside this model).
So if next_shoeing is an instance of Date it won't be invalid anyway.

NSDateFormatter date format string

I have an odd issue with an NSDateFormatter, I am passing the following string as a date format "dd/MM/yy"
If I enter 50 for the year I get a conversion to 1950 however anything below that for instance 49 results in 2049. Any ideas how I can remedy this?
Many thanks.
It sounds like you'll need to force a four digit response (or programmatically prepend two digits of "19") to wherever you're drawing your string from. Lots of people are using dates in the near to mid-term future like "12/21/12" (end of the Mayan Calendar era) so it's natural that a 2 digit year assumes 2000+ for digits 1-50 and 1999- for digits (50-99).
I'm also seeing a number of Google hits on the keyword terms "NSDateFormatter" & "century", b.t.w.

How does MongoDB compares the date only and ignores the time, such as date <= '2010-09-10'?

For some reason:
Analytic.where({:ga_date.gte => '2010-09-01'}).count() # greater than or equal to
gives back 0, but
Analytic.where({:ga_date.gte => Time.parse('2010-09-01')}).count()
gives back 230, which is the number of records (documents).
Actually, the first line on the top works in another case, so it is quite strange.
Can only the date be compared, because if it is
Analytic.where({:ga_date.lte => Time.parse('2010-09-10')}).count() # less than or equal to
then all the records with date 2010-09-10 will not be counted because Time.parse('2010-09-10') will give 2010-09-10 00:00:00, so the records will all have to be 2010-09-09 before the midnight. In other words, 2010-09-10 2am won't be included because 2am is not "less than or equal to" 00:00:00. It can be hacked by using
Analytic.where({:ga_date.lte => Time.parse('2010-09-10 23:59:59')}).count()
but it is kind of ugly. If there is a way to compare by date only like the first line of code in this post?
I think that you have two separate issues here.
Different data types
The following two lines are not equivalent. The first is a string comparison. The second is a comparison with a date object.
Analytic.where({:ga_date.gte => '2010-09-01'}).count()
Analytic.where({:ga_date.gte => Time.parse('2010-09-01')}).count()
I think you have figured this out, but it's important to be clear here. If you are storing date objects in the DB, you need to perform comparisons with date objects.
MongoDB will compare types and data.
Mismatch date storage
You are storing dates that have information for hours, minutes and seconds. However, you don't like the following notation:
:ga_date.lte => Time.parse('2010-09-10 23:59:59')
The workaround here is to use $lt and the day after.
:ga_date.lt => (Time.parse('2010-09-10') + 1.day) # or (60 * 60 * 24)
to add,
it is not strangely works, its coincidentally works when it just happens the string representation of the date happens to also lexicographically be 'greater than' the other date
other issue,
try to use only as much data fields as needed
if you meant it to be "within the calendar day",
what I usually like is to call beginning_of_day in both cases to equalize
this has the effect of neutralizing the minutes
else if you really meant within a 24h strike zone,
use ActiveSupport's '+ 1.day'

Resources