I have a Rails 3.2 app with an Article model that has a field for date. I want to create a scope that will retrieve all records from last month. The problem I'm having is that my current scope is not including the first day of the month.
# article.rb
scope :last_month, lambda { { conditions: { date: last_month_range } } }
private
def self.last_month_range
1.month.ago.beginning_of_month..1.month.ago.end_of_month
end
When I run this it does this:
SELECT * FROM 'articles' WHERE ('articles`.'date' BETWEEN '2013-07-01 07:00' AND '2013-08-01 06:59:59')
When I look at the actual results it only starts with the articles with a date of 07-02-2013.
However, if I change the code to:
def self.last_month_range
(1.month.ago.beginning_of_month - 1.day)..1.month.ago.end_of_month
end
It does this:
SELECT * FROM 'articles' WHERE ('articles`.'date' BETWEEN '2013-06-30 07:00' AND '2013-08-01 06:59:59')
In that case it pulls in articles with dates of 6/31/2013.
Can someone please recommend a fix? Thank you!
ActiveRecord does the conversion automatically to UTC, unless you have specified otherwise. You need to convert the range to UTC.
def self.last_month_range
1.month.ago.utc.beginning_of_month..1.month.ago.utc.end_of_month
end
I got bit by a bug in the accepted answer:
Time.now.utc # => 2015-05-01 01:18:57 UTC
1.month.ago.utc.beginning_of_month #=> 2015-03-01 00:00:00 UTC
The problem is that the time is being converted to utc after the month is subtracted.
Here's a correct solution:
def self.last_month_range
Time.now.utc.prev_month.beginning_of_month..Time.now.utc.prev_month.end_of_month
end
Related
I'm looking for some conditions to start billing my clients.
Every time that my client make a contract with me, i initialize a date in the attribute start_billing_at. I'd like to know now, if the start_billing_at attribute has been initialized during the previous month. Hope that my problem is more clear now
Thx for help
EDIT
I'v got to know if my date is between the first and the last day of the previous month
Subtracting two dates and calling to_i on it will give you the difference in days, you can switch on that:
if (Date.today - other_date).to_i < 30
# less than a month
else
# more than a month
end
Of course this doesn't exactly follow the months, but for my use cases it's generally good enough.
Another alternative is:
if date_to_check > Date.today.last_month
# within the last month
end
Or checking for inclusion in the range of last month's dates:
last_month = Date.today.last_month
(last_month.beginning_of_month..last_month.end_of_month).cover?(date_to_check)
%w|2017-07-01 2017-06-01|.map do |d|
(Date.today.month - Date.parse(d).month) % 12 == 1
end
#⇒ [true, false]
Here is my solution, based on Michael kohl solutions
def calcul_bill(date)
if Time.zone.today.last_month.strftime('%b, %Y') == date.strftime('%b, %Y')
#Do actions
else
#Do other actions
end
end
My date format is "Wed, 30 Aug 2017" is this case, so i just compare Month and year
I believe I would go for:
start_billing_at.beginning_of_month == Date.today.last_month.beginning_of_month
With a refinement you could define a method on date that allows you to:
start_billing_at.last_month?
So:
module BillingDateExtensions
refine Date do
def last_month?
self.beginning_of_month == Date.today.last_month.beginning_of_month
end
end
end
... and you can allow this to be mixed in to Date where you need it with:
using BillingDateExtensions
I know there is a method to determine if a certain time is on Daylight Savings Time (Time.now.dst?) but is there a method to give us the next date when Daylight Savings will change?
For example, Google returns Sunday, November 1 as the next Daylight Savings Time change in 2015.
Since these are dates that are based on other values, like the timezone you are working with, it requires a module like ActiveSupport/TZInfo.
require 'active_support/core_ext/time/zones'
tz = TZInfo::Timezone.get('US/Pacific')
# pick a timezone to work with
tz.current_period #returns an object of the current period
=> #<TZInfo::TimezonePeriod: #<TZInfo::TimezoneTransitionDefinition:
#<TZInfo::TimeOrDateTime: 1425808800>,#<TZInfo::TimezoneOffset: -28800,3600,PDT>>,
#<TZInfo::TimezoneTransitionDefinition: #<TZInfo::TimeOrDateTime: 1446368400>,
#<TZInfo::TimezoneOffset: -28800,0,PST>>>
tz.current_period.local_start.to_s
# => "2015-03-08T03:00:00+00:00"
tz.current_period.local_end.to_s
# => "2015-11-01T02:00:00+00:00"
One thing I haven't figured out is that since regular Ruby Core does this:
Time.now.dst?
# => true
Where is it getting this info? I found the TZInfo classes through ActiveSupport. Is Ruby just getting a boolean value from the OS?
How about this extension of the Time class:
class Time
class << self
def next_dst_change
startdate = Date.today
enddate = Date.today.end_of_year
match = Date.today.to_time.dst? ? false : true
startdate.upto(enddate).find { |date| date.to_time if date.to_time.dst? == match }
end
end
end
Now you can do Time.next_dst_change. You can apply this on your own timezone only but it solves your problem.
I have a Location that can have Events. I want to have an upcoming_events method but want it to round down such that if someone looks at 10pm at night, it will show todays events. I have this:
def upcoming_events
d=Time.new
d.strftime("%m-%d-%Y")
l=Event.where('location_id=? and start_datetime>?',self.id, d)
end
I gets converted down correctly but in d.strftime but the query is:
SELECT `events`.* FROM `events` WHERE (location_id=301 and start_datetime>'2012-06-20 02:49:23')
Any idea how to just get it to do '2012-06-20'?
Calling strftime does essentially nothing here, since it doesn't change the d object in any way.
Anyway, Rails provides the method beginning_of_day on DateTime (as well as Date & Time) that does exactly what you want:
d = Time.now #=> 2012-06-19 23:05:54 -0400
d.beginning_of_day #=> 2012-06-19 00:00:00 -0400
So just change your code to:
Event.where('location_id=? and start_datetime>?', self.id, d.beginning_of_day)
I think you actually meant "%Y-%m-%d" vice "%m-%d-%Y" since you wanted '2012-06-20'. As such, try the following:
def upcoming_events
d=Time.new
l=Event.where('location_id=? and start_datetime>?', self.id, d.strftime("%Y-%m-%d"))
end
I am implementing a full text search API for my rails apps, and so far have been having great success with Thinking Sphinx.
I now want to implement a date range search, and keep getting the "bad value for range" error.
Here is a snippet of the controller code, and i'm a bit stuck on what to do next.
#search_options = { :page => params[:page], :per_page => params[:per_page]||50 }
unless params[:since].blank?
# make sure date is in specified format - YYYY-MM-DD
d = nil
begin
d = DateTime.strptime(params[:since], '%Y-%m-%d')
rescue
raise ArgumentError, "Value for since parameter is not a valid date - please use format YYYY-MM-DD"
end
#search_options.merge!(:with => {:post_date => d..Time.now.utc})
end
logger.info #search_options
#posts = Post.search(params[:q], #search_options)
When I have a look at the log, I am seeing this bit which seems to imply the date hasn't been converted into the same time format as the Time.now.utc.
withpost_date2010-05-25T00:00:00+00:00..Tue Jun 01 17:45:13 UTC 2010
Any ideas? Basically I am trying to have the API request pass in a "since" date to see all posts after a certain date. I am specifying that the date should be in the YYYY-MM-DD format.
Thanks for your help.
Chris
EDIT: I just changed the date parameters merge statement to this
#search_options.merge!(:with => {:post_date => d.to_date..DateTime.now})
and now I get this error
undefined method `to_i' for Tue, 25 May 2010:Date
So obviously there is something still not setup right...
lets say d = "2010-12-10"
:post_date => (d.to_time.to_i..Time.now.to_i) would have gotten you there. I just did this in my project and it works great
I finally solved this, but it takes a slightly different approach but it works fine.
I was trying to put the date-range search inside a sphinx_scope (in the model) or as a :condition or :with (in the controller). This did not work, so instead I had to implement it inside the define_index in the model.
So what I did was put a check in the define_index to see if a record fell within a date range, the date range being defined by some SQL code, as shown below. In this case, I wanted to see if "start_date" fell within a date between now and 30 days ago, and an "end_date" fell within today and 30 days from now.
If the dates fell within the ranges, the code below causes the :live to be 0 or 1, depending on whether it falls outside or inside the date ranges (respectively):
define index do
# fields:
...
# attributes:
has "CASE WHEN start_date > DATE_ADD(NOW(), INTERVAL -30 DAY) AND end_date < DATE_ADD(NOW(), INTERVAL 30 DAY) THEN 1 ELSE 0 END", :type => :integer, :as => :live
...
# delta:
...
end
Then in your controller, all you have to do is check if :live => 1 to obtain all records that have start_dates and end_dates within the date ranges.
I used a sphinx_scope like this:
sphinx_scope(:live) {
{ :with => { :live => 1 } }
}
and then in my controller:
#models = Model.live.search(...)
To make sure it works well, you of course need to implement frequent reindexing to make sure the index is up to date, i.e. the correct records are :live => 1 or 0!
Anyway, this is probably a bit late for you now, but I implemented it and it works like a charm!!!
Wouldn't it work if you replaced
d = DateTime.strptime(params[:since], '%Y-%m-%d')
by
Time.parse(params[:since]).strftime("%Y-%m-%d")
(It seems the first one doesn't return a date in the expected format)
I'm implementing the Timezone support in Rails 2.1+, and I've run into an apparent bug in the way the data is pulled from the db. Let me try to set it up.
The "deals" model contains an "offer_date" datetime field. Let's say I have two records with these offer_dates:
Deal 1: 2009-12-29 23:59:59 -0500
Deal 2: 2009-12-28 23:59:59 -0500
This is correct: dates should mark the last second of its given day.
Now, when it comes time to find the deal for today, I have the following AR query:
#deal = Deal.first(:conditions=>['offer_date > ?', Time.now.beginning_of_day], :order=>"offer_date ASC")
In this case, although it's the 29th today, it returns the record ostensibly for the 28th. Why? Here's what happens in the console:
>> #deal = Deal.first(:conditions=>['offer_date > ?', Time.now.beginning_of_day], :order=>"offer_date ASC")
=> #<Deal id: 2, <blah blah blah...>, offer_date: "2009-12-29 04:59:00">
It's shifting the time forward by 5 hours, putting yesterday's day into the next. But when I do this:
>> #deal.offer_date
=> Mon, 28 Dec 2009 23:59:00 EST -05:00
I get the right time. What the heck??? Suffice to say, I need that date to work properly, but I can't figure out where my issue is. Any assistance you can provide would be greatly appreciated!
See my prior rsponse on Time vs Time.zone for AR date queries.
Instead of using Time.now, try using Time.zone.now
Of course you have to set this and everything. This tutorial seems helpful.
the Time class doesn't care about the time zone in the implementation of #to_s, therefore you have to use
Time.zone.now # or any other TimeWithZone object
in your finders/named_scopes/find calls. Or you could read through http://marklunds.com/articles/one/402 and then put
module ActiveRecord
module ConnectionAdapters # :nodoc:
module Quoting
# Convert dates and times to UTC so that the following two will be equivalent:
# Event.all(:conditions => ["start_time > ?", Time.zone.now])
# Event.all(:conditions => ["start_time > ?", Time.now])
def quoted_date(value)
value.respond_to?(:utc) ? value.utc.to_s(:db) : value.to_s(:db)
end
end
end
end
into your environment.rb or into an initializer.