I'm trying to build a method where I pass a month and can then query for the previous month dynamically.
total_churn(month)
last_month = month - 1
companies = Company.where("created_at BETWEEN '#{last_month}' AND '#{month}')
return companies.count
end
How do I pass the method 'month' in a way where I can dynamically determine the last month using ruby on rails? Thanks
My suggestion: accept a date rather than a month.
total_churn(date)
month_previous = date - 1.month
companies = Company.where("created_at BETWEEN ? AND ?, '#{month_previous}', '#{date}')
return companies.count
end
Current month:
Time.now.month
Date.today.month
Time or day one month ago:
(Time.now - 1.month).month
(Date.today - 1.month).month
...also equivalent to:
Time.now.month - 1
Date.today.month - 1
Previous month for any given date:
#date - 1.month
I would personally build your method to accept a date rather than just a month number. As long as the created_at field is storing dates, you'll need to give the query two dates for it to run, even if those dates are the 1st.
Rails has some helpful time helpers to create upper and lower bounds for your query. (beginning_of_month and end_of_month in the Time class)
This method is also properly escaped with question marks instead of string interpolation, which is open to SQL injection attacks.
def total_churn(month)
companies = Company.where('created_at BETWEEN ? and ?',(Time.now - 1.month).beginning_of_month,(Time.now - 1.month).end_of_month)
companies.count
end
I would also say that this will only work for the most recent year. If you want to be able to query for earlier data, you may want to either add a year param or simply pass in a date and let it use that in place of Time.now.
# with separate year and month params
def total_churn(month, year)
date = DateTime.parse("#{year}/#{month}/01")
companies = Company.where('created_at BETWEEN ? and ?',(date - 1.month).beginning_of_month,(date - 1.month).end_of_month)
companies.count
end
# with a random date input
def total_churn(date_in_month)
companies = Company.where('created_at BETWEEN ? and ?',(date_in_month - 1.month).beginning_of_month,(date_in_month - 1.month).end_of_month)
companies.count
end
You need at least 2 parameters to accomplish this, one for the month and one for the year. This is needed to establish what month you want.
def total_churn(year, month)
date = Date.new year, month
one_month_ago = date - 1.month
...
However, if you already have a date object, then you can use sscirrus' answer. If the date is in a string, you'd probably want to parse it first (like in your comment)
def total_churn(date_string)
date = Date.parse(date_string)
one_month_ago = date - 1.month
...
>> date_string = '2012-09-01 00:00:00.000000'
>> total_churn date_string
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'm new into HAML, and I have ( I hope easy ) question regarding date/ time. Is it possible to get start and end date of current week in HAML in not very very commplicated way? I want to use those dates for navigation in my calendar.
For month and year is rather easy:
- year = Date.today.year
- month = Date.today.month
- day = Date.today
- monthEnd = year.to_s + "-" + month.to_s + "-" + Integer(Date.new(year, month, -1).strftime("%d")).to_s
- monthStart = Date.new(year, month, 1)
- yearStart = year.to_s + "-01-01";
- yearEnd = year.to_s + "-12-31";
If it is not possible, I will do it in Javascript, but I would like to have this consistent.
In short, these are the methods you're looking for:
Date.today.beginning_of_week
Date.today.end_of_week
If you want to set the beginning of the week to sunday, you can do so in an initializer. Remember to restart your server.
initializers/set_beginning_of_week.rb
Date.today.beginning_of_week = :sunday
Further Improvements
The HAML syntax - is equivalent to ERB <% %>. So this is used for coding Ruby code in your views.
In your views you should not set a long list of variables or perform complex functions. You should try to limit these to if, else, each & other basic operations.
Additionally, for these type of functions you can call helper methods.
I'd setup some basic helpers like:
helpers/application_helper.rb
def current_date
Date.today # or use Time.zone.today as Rob suggested
end
def current_month
current_date.month
end
def current_year
current_date.year
end
Resulting in easy calls
current_year
current_month
current_date
current_date.beginning_of_week
current_date.end_of_week
current_month.beginning_of_month
current_month.end_of_month
current_year.beginning_of_year
current_year.end_of_year
This can then be implemented in any view.
views/foo/show.html.haml
.calendar_header
- current_year
%p Beginning of month is
- current_month.beginning_of_month
Assuming the week starts on Sunday and ends on Saturday, and you want to use Ruby's standard library:
- weekStart = Date.today.prev_day(Date.today.cwday)
- weekEnd = Date.today.next_day(6-Date.today.cwday)
BTW, I would probably take advantage of Time.zone.now instead of Date.today if you're using Rails. Same idea for your other ones. More on that in this blog.
#TheChamp:
Rails has convenience methods for doing just this:
weekStart = Time.zone.today.beginning_of_week
weekEnd = Time.zone.today.end_of_week
This isn't really a HAML issue, it's more a Ruby and/or Rails issue. Have a look at the rails date and time helpers:
http://api.rubyonrails.org/classes/DateAndTime/Calculations.html
They allow you to do:
week_start = Time.now.beginning_of_week
There are some stackoverflow posts related to my question but not all that similar.
I would like an efficient and somewhat elegant(if possible) solution as to get an array of missing dates after comparing a user specified date range to the summary table in postgresql. One method I know of is to lay the range out into a list of dates and then compare individually to all the dates by querying EXIST or if result == nil?/empty?, etc. But if user was to do a large range, this could be resource consuming and slow.
Is there any methods beside the ones that are currently listed?
Thank you
First, we need to sort the dates. In ruby this is as simple as
sorted_dates = dates.sort
If you know the dates are sorted, then just start with the first date and increment by one as you iterate through your date range. If the next date in your array is not the date you expected, add the missing date to your missing_dates array, and continue incrementing until you reach the date included.
This code might look like the following:
def find_missing_dates(sorted_dates)
current_date = sorted_dates[0]
missing_dates = Set.new
sorted_dates.each do |date|
while current_date != date
missing_dates << current_date
current_date += 1.day
end
current_date += 1.day
end
end
This is O(N) for average case, so to get it more efficient, we could split in half and recurse.
def dates_between(lower, upper)
(lower..upper).to_a - [lower,upper]
end
def find_missing_dates(sorted_dates, missing_dates = Set.new)
min_date = sorted_dates[0]
max_date = sorted_dates[-1]
if (min_date - max_date).to_i == (sorted_dates.count - 1)
missing_dates
else
middle_date_lower = sorted_dates[sorted_dates.count / 2 - 1]
middle_date_upper = sorted_dates[sorted_dates.count / 2]
unless (middle_date_upper - middle_date_lower) == 1
missing_dates.merge(dates_between(middle_date_lower, middle_date_upper))
end
find_missing_dates(sorted_dates[0..(sorted_dates.count/2 - 1)], missing_dates).merge(find_missing_dates(sorted_dates[(sorted_dates.count/2)..-1]))
end
end
find_missing_dates(sorted_dates)
This is still worst case O(N), but average case is O(log N)
Example I have:
range = start.to_date..(end.to_date + 1.day)
end and start are dates.
How do I create a month array based on this range?
Example:
I have the dates 23/1/2012 and 15/3/2012
The months are Januar, Februar and Marts.
I want to get a array like ["1/1/2012", "1/2/2012", "1/3/2012"]
and if the range was betweeen 25/6/2012 to the 10/10/2012
the array would be: ["1/6/2012", "1/7/2012", "1/8/2012", "1/9/2012", "1/10/2012"]
require 'date'
date_from = Date.parse('2011-10-14')
date_to = Date.parse('2012-04-30')
date_range = date_from..date_to
date_months = date_range.map {|d| Date.new(d.year, d.month, 1) }.uniq
date_months.map {|d| d.strftime "%d/%m/%Y" }
# => ["01/10/2011", "01/11/2011", "01/12/2011", "01/01/2012",
# "01/02/2012", "01/03/2012", "01/04/2012"]
Rails ActiveSupport core extensions includes a method for Date: beginning_of_month. Your function could be written as follows:
def beginning_of_month_date_list(start, finish)
(start.to_date..finish.to_date).map(&:beginning_of_month).uniq.map(&:to_s)
end
Caveats: this could be written more efficiently, assumes start and finish are in the expected order, but otherwise should give you the months you're looking for. You could also rewrite to pass a format symbol to the #to_s method to get the expected month format.
I was curious about performance here so I tested some variations. Here's a solution better optimized for performance (about 8x faster in my benchmark than the accepted solution). By incrementing by a month at a time we can remove the call to uniq which cuts quite a bit of time.
start_date = 1.year.ago.to_date
end_date = Date.today
dates = []
date = start_date.beginning_of_month
while date <= end_date.beginning_of_month
dates << date.to_date.to_s
date += 1.month
end
dates
#=> ["2019-02-01", "2019-03-01", "2019-04-01", "2019-05-01", "2019-06-01", "2019-07-01", "2019-08-01", "2019-09-01", "2019-10-01", "2019-11-01", "2019-12-01", "2020-01-01", "2020-02-01"]
Benchmark Results:
Comparison:
month increment loop: 17788.3 i/s
accepted solution: 2140.1 i/s - 8.31x slower
gist of the benchmark code
Similar to one of the solutions above using beginning_of_month .. but taking less space (by using Set) and is neater for using inject.
(start_month..end_month).inject(Set.new) { |s, i| s << i.beginning_of_month; s }.to_a
This question was here for other languages, so let here be one for Ruby.
How do I calculate number of complete years that have passed from a given date? As you probably have guessed, that's to calculate person's age automatically. The closest one is distance_of_time_in_words Rails helper, so the following template
Jack is <%= distance_of_time_in_words (Time.now, Time.local(1950,03,22)) %> old.
yields
Jack is over 59 years old.
But I need more precise function that yields just number. Is there one?
If there exists some kind of Ruby on Rails helper function for this, this is OK, although pure Ruby solution would be better.
Edit: the gist of the question is that a non-approximate solution is needed. At the 2nd of March Jack should be 59 years old and the next day he should be 60 years old. Leap years and such should be taken into account.
Do you want age as people typically understand it, or are you looking for a precise measure of time elapsed? If the former, there is no need to worry about leap years and other complications. You simply need to compute a difference in years and reduce it if the person has not had a birthday yet this year. If the latter, you can convert seconds elapsed into years, as other answers have suggested.
def age_in_completed_years (bd, d)
# Difference in years, less one if you have not had a birthday this year.
a = d.year - bd.year
a = a - 1 if (
bd.month > d.month or
(bd.month >= d.month and bd.day > d.day)
)
a
end
birthdate = Date.new(2000, 12, 15)
today = Date.new(2009, 12, 14)
puts age_in_completed_years(birthdate, today)
require 'date'
def years_since(dt)
delta = (Date.today - Date.parse(dt)) / 365
delta.to_i
end
I have a gem/plugin called dotiw that has a distance_of_time_in_words_hash that will return a hash like: { :years => 59, :months => 11, :days => 27 }. From that you could work out if it's near a certain limit.
An approach that handles leap years
Whenever you're calculating elapsed years since a date, you have to decide how to handle leap year. Here is my approach, which I think is very readable, and is able to take leap years in stride without using any "special case" logic.
def years_completed_since(start_date, end_date)
if end_date < start_date
raise ArgumentError.new(
"End date supplied (#{end_date}) is before start date (#{start_date})"
)
end
years_completed = end_date.year - start_date.year
unless reached_anniversary_in_year_of(start_date, end_date)
years_completed -= 1
end
years_completed
end
# No special logic required for leap day; its anniversary in a non-leap
# year is considered to have been reached on March 1.
def reached_anniversary_in_year_of(original_date, new_date)
if new_date.month == original_date.month
new_date.day >= original_date.day
else
new_date.month > original_date.month
end
end
I came up with the following, based on a similar reasoning as #FMc
First, compute the diff between today's year and birthday's year. Then, sum it to birthday and check the resulting date: if it's greater than today, decrease diff by 1.
To be used in Rails apps as it relies on ActiveSupport's years method
def age(birthday, today)
diff = today.year - birthday.year
(birthday + diff.years > today ) ? (diff - 1) : diff
end
Same idea as FM but with a simplified if statement. Obviously, you could add a second argument instead of using current time.
def age(birthdate)
now = DateTime.now
age = now.year - birthdate.year
age -= 1 if(now.yday < birthdate.yday)
age
end
withing http://github.com/radar/dotiw
Jack is <%= distance_of_time_in_words (Time.now, Time.local(1950,03,22)) %> old.
produce
Jack is 60 years old
you can use the ruby gem adroit-age
It works for leap years also..
age = AdroitAge.find_age("23/01/1990")
Update
require 'adroit-age'
dob = Date.new(1990,1,23)
or
dob = "23/01/1990".to_date
age = dob.find_age
#=> 23
I think this will always work, even for someone with a birthday near a leap day:
require 'date'
def calculate_age(start_date, end_date)
end_date.year - start_date.year - ((end_date.month > start_date.month || (end_date.month == start_date.month && end_date.day >= start_date.day)) ? 0 : 1)
end
puts calculate_age( Date.strptime('03/02/1968', '%m/%d/%Y'), Date.strptime('03/02/2010', '%m/%d/%Y'))
The calculated age with this method in the example call above is 42, which is correct despite 1968 being a leap year and the birthday being near a leap day.
Plus, this way there is no need to create a local variable.
How about this:
def age_in_years(date)
# Difference in years, less one if you have not had a birthday this year.
today = Date.today
age = today.year - date.year
age = age - 1 if [date.day, date.month, today.year].join('/').to_date > Date.today
end
d2.year - d1.year - (d2.month > d1.month || (d2.month == d1.month && d2.day >= d1.day) ? 0 : 1)
How about something like:
def years_diff(from_time,to_time)
(((to_time - from_time).abs)/ (365 * 24 * 60 * 60)).to_i
end
years_diff(Time.now,Time.local(1950,03,22)) #=> 59
years_diff(Time.now,Time.local(2009,03,22)) #=> 0
years_diff(Time.now,Time.local(2008,03,22)) #=> 1
To calculate number of Years and Months between two dates, I used this function
def calculate_year_month(from_date, to_date)
num_days = (from_date - to_date).to_i
num_months = (num_days / 30)
num_years = (num_months / 12)
num_months = (num_months % 12)
return num_years.to_s + " year(s) and " + num_months.to_s + " month(s)"
end