Skip to content

Commit

Permalink
Honor TZ when searching on temporal fields by relative dates
Browse files Browse the repository at this point in the history
Previously queries like 'yesterday' would resolve to only a specific
date, discarding any information about the user's time zone. After this
change, datetime and timestamp fields take the time zone into
consideration.
  • Loading branch information
adamruzicka committed May 29, 2023
1 parent 10d1186 commit 2158fec
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 5 deletions.
18 changes: 13 additions & 5 deletions lib/scoped_search/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,13 @@ def default_fields_for(value, operator = nil)
# In case Time responds to #zone, we know this is Rails environment and we can use Time.zone.parse. The benefit is that the
# current timezone is respected and does not have to be specified explicitly. That way even relative dates work as expected.
def parse_temporal(value)
return Date.current if value =~ /\btoday\b/i
return 1.day.ago.to_date if value =~ /\byesterday\b/i
return 1.day.from_now.to_date if value =~ /\btomorrow\b/i
return date_with_timezone(Date.current) if value =~ /\btoday\b/i
return date_with_timezone(1.day.ago) if value =~ /\byesterday\b/i
return date_with_timezone(1.day.from_now) if value =~ /\btomorrow\b/i
return (eval($1.strip.gsub(/\s+/,'.').downcase)).to_datetime if value =~ /\A\s*(\d+\s+\b(?:hours?|minutes?)\b\s+\bago)\b\s*\z/i
return (eval($1.strip.gsub(/\s+/,'.').downcase)).to_date if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bago)\b\s*\z/i
return date_with_timezone(eval($1.strip.gsub(/\s+/,'.').downcase)) if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bago)\b\s*\z/i
return (eval($1.strip.gsub(/from\s+now/i,'from_now').gsub(/\s+/,'.').downcase)).to_datetime if value =~ /\A\s*(\d+\s+\b(?:hours?|minutes?)\b\s+\bfrom\s+now)\b\s*\z/i
return (eval($1.strip.gsub(/from\s+now/i,'from_now').gsub(/\s+/,'.').downcase)).to_date if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bfrom\s+now)\b\s*\z/i
return date_with_timezone(eval($1.strip.gsub(/from\s+now/i,'from_now').gsub(/\s+/,'.').downcase)) if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bfrom\s+now)\b\s*\z/i
if Time.respond_to?(:zone) && !Time.zone.nil?
parsed = Time.zone.parse(value) rescue nil
parsed && parsed.to_datetime
Expand Down Expand Up @@ -334,6 +334,14 @@ def register_named_scope! # :nodoc

search_scope
end

# This should get called only when the relative specifiers (1 day ago and
# so on), this code path is only viable when Rails are available
def date_with_timezone(datetime)
date = datetime.to_date
return datetime unless date.respond_to?(:in_time_zone)
date.in_time_zone.to_datetime
end
end

# Registers the complete_for method within the class that is used for searching.
Expand Down
6 changes: 6 additions & 0 deletions spec/integration/ordinal_querying_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@
@class.search_for('timestamp > "3 hours ago"').length.should == 4
end

it "should take timezone into consideration" do
now = Time.now
expected = DateTime.new(now.year, now.month, now.day, 0, 0, 0, now.zone).utc.strftime('%Y-%m-%d %H:%M:%S')
@class.search_for('timestamp > yesterday').to_sql.should include(expected)
end

it "should accept 1 week from now as date format" do
@class.search_for('date < "1 week from now"').length.should == 6
end
Expand Down

0 comments on commit 2158fec

Please sign in to comment.