Date comparison in Rails 3.2 - activerecord

Stage:
I have this model:
Promo(id: integer, start_date: datetime, end_date: datetime)
I want to know which current promotions.
May be our query should be like:
SELECT * FROM promos WHERE now BETWEEN start_date AND end_date;
Question:
How should I make it in Ruby?
Which is the correct way?
Thank you.

Rails is intelligent. If you retrieve a date/time, it will converted to DateTime object.
On contrary, passing DateTime object to the where clause (for Rails 3.x), it will correctly build the where clause.
String passed to where clause will be passed to the SQL where clause. If you pass an array,
second and later elements will be replaced with '?' character in the first element.
So, for your case:
Promo.where(["? between start_date and end_date", DateTime.now])
works. I verified on my Rails model(Position) which happen to have start_date and end_date, and works correctly:
[1] pry(main)> Position.where(["? between start_date and end_date", DateTime.now]).count
=> 2914
It's on Rails 3.2.8 with PostgreSQL.

Yes, you can use comparison operators to compare dates e.g.:
irb(main):018:0> yesterday = Date.new(2009,6,13)
=> #<Date: 4909991/2,0,2299161>
irb(main):019:0> Date.today > yesterday
=> true
But are you trying to compare a date to a datetime?
If that's the case, you'll want to convert the datetime to a date then do the comparison.
or
Date.parse('2010-11-01') < Date.today
will return true if '2010-11-01' has already passed
I hope this helps.

Haven't tested this, but try something like:
currentTime = Time.now()
Promo.find(
:all,
:conditions => ['start_date < ? AND end_date > ?', currentTime, currentTime]
)
You might need to use the Date class if the Time class doesn't work

Related

ruby check a range of dates using date object?

I am new to ruby. I am practising booking a holiday on a fake airbnb but I want to check if the dates I want (start_of_holiday to end_of_holiday) is available in the dates of the room(start_date - end_date) but I can't seem to compare the ranges or convert the dates into ranges. Is there a better way of checking? thank you
require 'date'
start_of_holiday = Date.parse("2022-10-02").strftime('%Y-%m-%d')
end_of_holiday = Date.parse("2022-10-05").strftime('%Y-%m-%d')
start_date = '2022-10-01'
end_date = '2022-10-11'
print true if (start_of_holiday..end_of_holiday).include?(start_date..end_date)
The problem is that you're passing a range to include? which will return false in this case as the whole range (start_date..end_date) is not in (start_of_holiday..end_of_holiday).
From the docs;
include?(obj) → true or false
Returns true if obj is an element of the range, false otherwise.
You could simply adjust it to check for start_of_holiday and end_of_holiday
(start_date..end_date).cover?(start_of_holiday) && (start_date..end_date).cover?(end_of_holiday)
# true

Is that good solution? [duplicate]

I have a record set that includes a date field, and want to determine how many unique dates are represented in the record set.
Something like:
Record.find(:all).date.unique.count
but of course, that doesn't seem to work.
This has changed slightly in rails 4 and above :distinct => true is now deprecated. Use:
Record.distinct.count('date')
Or if you want the date and the number:
Record.group(:date).distinct.count(:date)
What you're going for is the following SQL:
SELECT COUNT(DISTINCT date) FROM records
ActiveRecord has this built in:
Record.count('date', :distinct => true)
Outside of SQL:
Record.find(:all).group_by(&:date).count
ActiveSupport's Enumerable#group_by is indispensable.
the latest #count on rails source code only accept 1 parameter.
see: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-count
so I achieved the requirement by
Record.count('DISTINCT date')
Detailing the answer:
Post.create(:user_id => 1, :created_on => '2010-09-29')
Post.create(:user_id => 1, :created_on => '2010-09-29')
Post.create(:user_id => 2, :created_on => '2010-09-29')
Post.create(:user_id => null, :created_on => '2010-09-29')
Post.group(:created_on).count
# => {'2010-09-29' => 4}
Post.group(:created_on).count(:user_id)
# => {'2010-09-29' => 3}
Post.group(:created_on).count(:user_id, :distinct => true) # Rails <= 3
Post.group(:created_on).distinct.count(:user_id) # Rails = 4
# => {'2010-09-29' => 2}
As I mentioned here, in Rails 4, using (...).uniq.count(:user_id) as mentioned in other answers (for this question and elsewhere on SO) will actually lead to an extra DISTINCT being in the query:
SELECT DISTINCT COUNT(DISTINCT user_id) FROM ...
What we actually have to do is use a SQL string ourselves:
(...).count("DISTINCT user_id")
Which gives us:
SELECT COUNT(DISTINCT user_id) FROM ...
Also, make sure you have an index on the field in your db, or else that query will quickly become sloooow.
(It's much better to do this in SQL, otherwise you pull the entire db table into memory just to answer the count.)

Add Day to DateTime in EF Core 3 when Select executed

I am trying to query the database using EF Core 3, group DateTime field by Date and Hours and add Hour part to Date on Select. So far all my Linqs fail.
Fail case 1. Add hours to Date using AddHours.
Logs.GroupBy(g => new
{
g.DateStamp.Date,
g.DateStamp.Hour,
g.Result
}).Select(s => new
{
Date = s.Key.Date.AddHours(s.Key.Hour),
s.Key.Result,
Conversions = s.Count(),
Cost = s.Sum(sum => sum.ConversionCost)
}).OrderBy(o => o.Date)
Error:
The datepart hour is not supported by date function dateadd for data
type date.
Not sure exactly where is the problem, AddHours is not supported by Linq to Entity provider or s.Key.Date fields can't contain hours anymore because it becomes Date only field.
Fail case 2. Create new DateTime object and pass variables.
Logs.GroupBy(g => new
{
g.DateStamp.Date,
g.DateStamp.Hour,
g.Result
}).Select(s => new
{
Date =new DateTime(s.Key.Date.Year, s.Key.Date.Month, s.Key.Date.Day, s.Key.Hour, 0, 0),
s.Key.Result,
Conversions = s.Count(),
Cost = s.Sum(sum => sum.ConversionCost)
}).OrderBy(o => o.Date)
Error:
InvalidOperationException: The LINQ expression
'OrderBy<<>f__AnonymousType1, DateTime>(
source: Selectf__AnonymousType0, Log>, <>f__AnonymousType1>(
source: GroupByf__AnonymousType0, Log>(
source: DbSet,
keySelector: (l) => new {
Date = l.DateStamp.Date,
Hour = l.DateStamp.Hour,
Result = l.Result
},
elementSelector: (l) => l),
selector: (e) => new {
Date = new DateTime(
e.Key.Date.Year,
e.Key.Date.Month,
e.Key.Date.Day,
e.Key.Hour,
0,
0
),
Result = e.Key.Result,
Conversions = Count(e),
Cost = Sum(
source: e,
selector: (sum) => sum.ConversionCost)
}),
keySelector: (e0) => e0.Date)' could not be translated. Either rewrite the query in a form that can be translated, or switch to
client evaluation explicitly by inserting a call to either
AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See
https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
The OrderBy(o => o.Date) stops working and gives an exception, why? I have no idea either.
So the final question would be, how to group DateTime field from SQL Database using EF Core by Date+Hour and move it to Select into one field and also perform OrderBy by new Date field at the end.
I'm able to comment on the fail case 1 as I've also encountered the same issue.
x.Date is converted to sql as CONVERT(date, x)
x.AddHours(y) is converted to sql as DATEADD(hour, x, y)
We have both, so it's DATEADD(hour, CONVERT(date, x), y) and it's not possible, as you can't add hours to data of type date hence the error message.
On the sql level it's usually fixed by adding additional cast to datetime2, sth like
CAST(CONVERT(date, x) AS datetime2)
So you deal on datetime2 level again and able to add hours to the resulting value.
Having said that I dunno how to force EF to generate additional cast (both (DateTime) or Convert.ToDateTime were of no help), so here is a workaround I came up with (can't say whether it could fail with overflow once)
x.AddMilliseconds(-EF.Functions.DateDiffMillisecond(TimeSpan.Zero, x.TimeOfDay))
.AddHours(y)
Tested it with EF Core 3.1.12 and it works for me as expected. This is the output
(DATEADD(
hour,
CAST(y AS int),
DATEADD(
millisecond,
CAST(
CAST(
-DATEDIFF(MILLISECOND, '00:00:00', CAST(x AS time)) AS float) AS int
),
x
)
)
Would guess that case 2 fails, as EF is not able to translate new DateTime expression, but it's just error being cryptic.
I had the same issue when using EFCore 6, I was getting an error doing a Date.AddHours because it looks like it cast to date instead of datetime. In order to get it to work I had to cast to object and then cast to DATETIME, then I was able to add my hours.
Logs.GroupBy(g => new
{
g.DateStamp.Date,
g.DateStamp.Hour,
g.Result
}).Select(s => new
{
Date = ((DateTime)(object)s.Key.Date).AddHours(s.Key.Hour),
s.Key.Result,
Conversions = s.Count(),
Cost = s.Sum(sum => sum.ConversionCost)
}).OrderBy(o => o.Date)

[ruby]Get file, parse text and create date object

I have a raw .txt file formatted as such:
01.01.2017;New Year
16.04.2017;Easter
25.12.2017;Christmas
(Sidenote: dates are formatted as dd.mm.yyyy)
I'm trying to read this file, slice the text per line and make a hash out of it, with the key being the date, and its value the name of the corresponding public holiday.
I've already gotten so far:
holidays = Hash[*File.read('holidays.txt').split(/;|\n/)]
This results in the dates being set as strings, not date objects.
Any ideas as to how I could then transform these strings to Date (or DateTime) objects?
P.S.: I'm only using Ruby, so no Rails helpers...
Something like this
holidays = File.read('holidays.txt').split(/\n/).map do |row|
date, holiday_name = row.split(';')
date = Date.parse(date, '%d.%m.%Y')
[date, holiday_name]
end.to_h
=> {
#<Date: 2017-01-01 ((2457755j,0s,0n),+0s,2299161j)> => "New Year",
#<Date: 2017-04-16 ((2457860j,0s,0n),+0s,2299161j)> => "Easter",
#<Date: 2017-12-25 ((2458113j,0s,0n),+0s,2299161j)> => "Christmas"
}

How to change date from "mm-dd-yy" to "yy-mm-dd"?

date = "21-12-2013"
in a db table, I have a date column with "20131221", and I need to compare the date. Is the format wrong? How can I change the format form dd-mm-yy to yy-mm-dd?
Using regular expression (String#sub):
date = "21-12-2013"
date.sub(/(\d+)-(\d+)-(\d+)/, '\3-\2-\1')
# => "2013-12-21"
Using DateTime::strptime and DateTime#strftime:
require 'date'
DateTime.strptime(date, '%d-%m-%Y').strftime('%Y-%m-%d')
# => "2013-12-21"

Resources