Rails: Date.today - 1.day - ruby-on-rails

Using the rails console, I just got bit by this:
Assume today is December 11.
Date.today-1.day # December 10 (no spaces)
Date.today - 1.day # December 10 (a space on both sides of the minus sign)
Date.today -1.day # December 11 whaaaat?
Date.today -5.days # Still december 11!
Can someone explain what's going on here? I am a little worried about how easy this could be missed in code. Any other suggestions on how to code this?

The difference you are seeing is caused by the way how ruby parses your code. In your case, you have stumbled about a syntactic nuance which greatly changes how the code is evaluated.
Your first two examples are actually expressions involving an operation on an object. Thus
Date.today-1.day
Date.today - 1.day
are both equivalent to
Date.today.send(:-, 1.day)
# a "fancy" way to write
Date.today - 1.day
Your second two examples
Date.today -1.day
Date.today -5.days
are actually direct method calls on the Date.today method and are thus equivalent to
Date.today( (-1).day )
This is because the minus is interpreted as a prefix to the 1 (and thus is parsed as the number -1). This interpretation is possible and doesn't throw any errors because of two properties of ruby:
Parenthesis are optional when calling methods as long as the result is unambiguous
Date.today accepts an optional argument denoting calendar used
The actual results you receive depend a bit on your environment as Rails seems to override some Date methods here. In plain Ruby, you would get different dates back but it would still work.

When you don't add a space after the minus sign, ruby takes it as a parameter for the function today. This function can receive one parameter. See here

Your problem is a little syntax problem.
This works fine for me
Date.today
# => Sun, 18 May 2014
Date.today() - 1.day
# => Sat, 17 May 2014
An prettier option is
Date.yesterday
# => Sat, 17 May 2014
Good luck!

Related

using eval to convert string into hash

I have string like this
a="{\"company\"=>\"khkhkh\", \"email\"=>\"hjkh#hkkj.kjh\",\"address\"=>\"yiyiyu\", \"date\"=>Mon, 28 Apr 2014 13:14:10 BST +01:00}"
but i have made a mistake when generating this string. that is, i appended the date without converting to string. So now if i try to get back this into a hash using eval(a) . This throws a error, with is acceptable.
SyntaxError: (eval):1: syntax error, unexpected tCONSTANT, expecting => ....
Is there any way to get that hash back, since iam in a situation that i cant regenerate this string.
Any help would be highly appreciable.
Thanks.
For your immediate predicament:
a.gsub(/\"date\"\s*=>(.*?)(\}|,\s*\")/, '"date"=>"\1"\2')
This should work even if the date is not the last entry of the hash.
For the next time around: It is really not a good idea to serialize data by manually turning them into code that you eval later.
In most cases your best bet is probably to just generate JSON and parse that later. Using a proper JSON serializer/generator will also make sure your data is syntactically correct.
If the date is always last you could go with the straightforward approach:
date = a.gsub(/[\{\}]/, '').split('=>').last
a.gsub(date, "\"#{date}\"")
Will return
"{\"company\"=>\"khkhkh\", \"email\"=>\"hjkh#hkkj.kjh\",\"address\"=>\"yiyiyu\", \"date\"=>\"Mon, 28 Apr 2014 13:14:10 BST +01:00\"}"
If you have multiple dates, try to replace them with quoted dates by regexp:
wrong_data = "" # your string
correct_data = wrong_data.gsub(/(\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} \w{3} \+\d{2}:\d{2})/, '"\1"')
eval(correct_data)
PS. As #Gene truly noticed, you should avoid using eval in your code.

write Regex to find match

I never wrote any complex regular expression before, and what I need seems to be (at least) a bit complicated.
I need a Regex to find matches for the following:
"On Fri, Jan 16, 2015 at 4:39 PM"
Where On will always be there;
then 3 characters for week day;
, is always there;
space is always there;
then 3 characters for month name;
space is always there;
day of month (one or two numbers);
, is always there;
space is always there;
4 numbers for year;
space at space always there;
time (have to match 4:39 as well as 10:39);
space and 2 caps letters for AM or PM.
Here's a very simple and readable one:
/On \w{3}, \w{3} \d{1,2}, \d{4} at \d{1,2}:\d{2} [AP]M/
See it on rubular
Try this:
On\s+(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (?:Jan|Feb|Mar|Apr|May|June|July|Aug|Sept|Oct|Nov|Dec) \d{1,2}, \d{4} at \d{1,2}:\d{2} (?:AM|PM)
/On \w{3}, \w{3} \d{1,2}, \d{4} at \d{1,2}:\d{1,2} [A-Z]{2}/
# \w{3} for 3 charecters
# \d{1,2} for a or 2 digits
# \d{4} for 4 digits
# [A-Z]{2} for 2 capital leters
You could try the below regex and it won't check for the month name or day name or date.
^On\s[A-Z][a-z]{2},\s[A-Z][a-z]{2}\s\d{1,2},\s\d{4}\sat\s(?:10|4):39\s[AP]M$
DEMO
You can use Rubular to construct and test Ruby Regular Expressions.
I have put together an Example: http://rubular.com/r/45RIiwheqs
Since it looks you try to parse dates, you should use Date.strptime.
/On [A-Za-z]{3}, [A-Za-z]{3} \d{1,2}, \d{4} at \d{1,2}:\d{1,2}/g
The way you are describing the problem makes me thing that the format will always be preserved.
I would then in your case use the Time.parse function, passing the format string
format = "On %a, %b"On Fri, Jan 16, 2015 at 4:39 PM", format)
which is more readable than a regexp (in my opinion) and has the added value that it returns a Time object, which is easier to use than a regexp match, in case you need to perform other time-based calculations.
Another good thing is that if the string contains an invalid date (like "On Mon, Jan 59, 2015 at 37:99 GX" ) the parse function will raise an exception, so that validation is done for free for you.

How to display DateTimes with am/pm without using custom formatting in Rails

Currently I display my DateTimes in Rails with:
my_time.to_formatted_s(:long_ordinal)
That produces a date like:
March 17th, 2014 14:44
Perfect except for the 14:44 time. I want it to say 2:44pm (how the 'pm' is formatted doesn't matter to me). This needs to be easier for a common person to read. I know I can use:
my_time.strftime('%l:%M %p')
However, I'd really like to use a predefined symbol for a more human readable format, because I'd like this to be more human readable in any language. If someone is browsing from a different language where DateTimes naturally look a little different, I'm hoping Rails can, or at least be set to, automatically display the DateTime nicely. Maybe I'm expecting too much of Rails there? Seems strange to me though that there isn't a simple flag or setting for a more human readable DateTime format.
in that case you can add your own humanized format
# config/initializers/time_formats.rb
Time::DATE_FORMATS[:custom_long_ordinal] = "%B %e, %Y %l:%M %p"
and use it with
my_time.to_formatted_s(:custom_long_ordinal)
Refer to the api doc reference for ActiveSupport DateTime formatting: http://apidock.com/rails/ActiveSupport/CoreExtensions/DateTime/Conversions/to_formatted_s
If you just want 10:15 pm
You could use:
t=Time.now
am=true
if t.hour >= 12
t=t-12*60*60
am=false
end
s=t.to_s[13..17]
if am==true
return s+ "am"
else
return s+ "pm"
end
Or edit/expand the Time-Class with SOMETHING LIKE (I would not advice you use this code, it's just a draft)
class Time
def self.humanize
t=self
am=true
if t.hour >= 12
t=t-12*60*60
am=false
end
s=t.to_s[13..17]
if am==true
return s+ "am"
else
return s+ "pm"
end
end
end
It sounds like you'll be better reading up on Rails' i18n
We format our times using the config/locales/xx.yml file:
#config/locals/en.yml
en:
time:
formats:
small: "%-dM %-dD %-dW"
This allows us to call:
<%=l object, format: :small %>
You can then use strftimer to get some really nice formatting

How do I parse a translated date in Ruby on Rails?

I have configured an application in Ruby on Rails with translations to Spanish.
Now I need to parse a translated date, for example:
Jueves, 22 de Noviembre del 2012
I'm trying to do it this way:
Date.strptime('Jueves, 22 de Noviembre, 2012', '%A, %e de %B, %Y')
But it throws an invalid date error.
How can I do it?
Date::parse should understand Spanish. However, that de seems to throw the parser off. If you could get it into this format, this will work
Date.parse "Jueves, 22 Noviembre, 2012"
=> Thu, 22 Nov 2012
I had a very similar problem and I wrote a gem specifically for the purpose of parsing any non-English textual dates (that use Gregorian calendar), it's called Quando.
The gem readme is very informative and has code examples, but in short, this is how it works:
require 'quando'
Quando.configure do |c|
# First, tell the library how to identify Spanish months in your dates:
c.jan = /enero/i
c.feb = /febrero/i
c.mar = /marzo/i
c.apr = /abril/i
c.may = /mayo/i
c.jun = /junio/i
c.jul = /julio/i
c.aug = /agosto/i
c.sep = /septiembre/i
c.oct = /octubre/i
c.nov = /noviembre/i
c.dec = /diciembre/i
# Then, define pattern matchers for different date variations that you need to parse.
# c.day is a predefined regexp that matches numbers from 1 to 31;
# c.month_txt is a regexp that combines all month names that you previously defined;
# c.year is a predefined regexp that matches 4-digit numbers;
# c.dlm matches date parts separators, like . , - / etc. See readme for more information.
c.formats = [
/#{c.day} \s #{c.month_txt} ,\s #{c.year} $/xi, # matches "22 Mayo, 2012" or similar
/#{c.year} - #{c.month_txt} - #{c.day}/xi, # matches "2012-Mayo-22" or similar
# Add more matchers as needed. The higher in the order take preference.
]
end
# Then parse the date:
Quando.parse('Jueves, 22 Noviembre, 2012') # => #<Date: 2012-11-22 …>
You can redefine the matchers for date parts and formats partially or entirely, both globally or just for a single parser instance (to preserve your global settings), and use all the power of regular expressions.
There are more examples in the readme and in the source code. Hope you'll find the library useful.
So, the answer is: it's not possible, at least right now.
The only way to have it working, is by using javascript on the client side, and convert the format to another one before sending the field to the server. Then Rails will not have any problem parsing it.

How do I do this? Model.where("created_at >= #{Time.now - 5.days}")

This has been bugging me for a while...
How can I string interpolate a datetime in Rails ActiveRecord queries?
# Works, but supeh ugleh:
Model.where("created_at >= ?", Time.now - 5.days)
# How do I do this?
Model.where("created_at >= #{Time.now - 5.days}")
# As is, it produces the following error message:
# ActiveRecord::StatementInvalid: PG::Error: ERROR: syntax error at or near ...
The reason I care is for code readability:
# I like this better:
Model.where("created_at >= #{Time.now - 5.days} OR" + \
"updated_at >= #{Time.now - 3.days}")
# than this:
Model.where("created_at >= ? OR updated_at >= ?", Time.now - 5.days, Time.now - 3.days)
Old question, but my favoured method is:
Model.where(created_at: 5.days.ago..Time.current)
Much prettier and more readable.
Also, Rails 3.2 introduced some Active Support helper methods to get some common ranges, Time#all_day, Time#all_week, Time#all_quarter and Time#all_year, so you could for instance do:
Model.where(created_at: Time.current.all_week)
I'd advise against using string interpolation for this, there are many sharp edges and you'll probably have more fun bobbing for apples in a bucket of fish hooks. You should do it this way:
Model.where(
'created_at >= :five_days_ago or updated_at >= :three_days_ago',
:five_days_ago => Time.now - 5.days,
:three_days_ago => Time.now - 3.days
)
Using (well) named placeholders gives you the readability and position independence that you think string interpolation offers but nicely sidesteps the quoting, timezone, and format issues that string interpolation forces on you.
But how do you safely use string interpolation? There are a few things you must handle yourself:
Quoting and escaping.
Timestamp formats.
Maybe timezones too.
ActiveRecord will take care of all this nonsense for you.
Don't try to do the quoting yourself, use the driver's quoting methods. You will have access to connection.quote for properly quoting strings.
Any database will know what to do with ISO 8601 timestamps and there is a convenient iso8601 method for that. ISO 8601 also conveniently includes the timezone and the database should be able to parse that (but if it can't then you'll have to convert your times to UTC by hand with .utc).
So, to be safe:
Model.where("created_at >= #{connection.quote((Time.now - 5.days).utc.iso8601)} " + \
"OR updated_at >= #{connection.quote((Time.now - 3.days).utc.iso8601)}")
Not so pretty now is it? With ISO 8601 timestamps you should be safe replacing the connection.quote calls with simple single quotes:
Model.where("created_at >= '#{(Time.now - 5.days).utc.iso8601}' " + \
"OR updated_at >= '#{(Time.now - 3.days).utc.iso8601}'")
but you still have a lot of noise and ugliness and you'll be developing bad habits.
We're not partying like PHP programmers in 1999 so don't give in to false laziness by using string interpolation in your SQL, use named placeholders.
You can use endless (>= Ruby 2.6) and beginless (>= Ruby 2.6) ranges:
Endless: Suffix it with the range operator .. if you want after some date:
Model.where(created_at: (Time.zone.now - 5.days)..)
Beginless: Prefix it with the range operator .. if you want before some date:
Model.where(created_at: ..(Time.zone.now - 5.days))

Resources