Is Time.zone.now.to_date equivalent to Date.today? - ruby-on-rails

Is Time.zone.now.to_date equivalent to Date.today?
Another way to put it: will Time.zone.now.to_date == Date.today always be true?
If not, what's the best way to get a Date object corresponding to "now" in the application time zone?

They are not always the same. Time.zone.now.to_date will use the applications time zone, while Date.today will use the servers time zone. So if the two lie on different dates then they will be different. An example from my console:
ruby-1.9.2-p290 :036 > Time.zone = "Sydney"
=> "Sydney"
ruby-1.9.2-p290 :037 > Time.zone.now.to_date
=> Wed, 21 Sep 2011
ruby-1.9.2-p290 :038 > Date.today
=> Tue, 20 Sep 2011

Even easier: Time.zone.today
I also wrote a little helper method Date.today_in_zone that makes it really easy to get a "today" Date for a specific time zone without having to change Time.zone:
# Defaults to using Time.zone
> Date.today_in_zone
=> Fri, 26 Oct 2012
# Or specify a zone to use
> Date.today_in_zone('Sydney')
=> Sat, 27 Oct 2012
To use it, just throw this in a file like 'lib/date_extensions.rb' and require 'date_extensions'.
class Date
def self.today_in_zone(zone = ::Time.zone)
::Time.find_zone!(zone).today
end
end

I think the best way is to learn the current time through:
Time.current
This will automatically check to see if you have timezone set then it will call Time.zone.now, but if you've not it will call just Time.now.
Also, don't forget to set your timezone in application.rb

# system timezone
Time.now.to_date == Date.today
# application timezone
Time.zone.now.to_date == Time.current.to_date == Time.zone.today == Date.current
http://edgeapi.rubyonrails.org/classes/Time.html#method-c-current
http://edgeapi.rubyonrails.org/classes/Date.html#method-c-current

Related

How to convert postgres UTC into another timezone?

Is there a way to convert postgres UTC time to the user's timezone or at the very least Eastern Standard Time (I'd be grateful with either answer)?
I thought that there was a way to change postgres UTC time, but I don't think there is without causing a lot of issues. From what I read it would be better to use code on the frontend that would convert it to the correct time zone?
This barely makes sense to me.
What's the point?
So that when a user checks off he completed a good habit, the habit disappears, and is suppose to reshow tomorrow at 12am, but the habits end up reshowing later in the day because of UTC.
habit.rb
scope :incomplete, -> {where("completed_at is null OR completed_at < ?", Date.today)} # aka those habits not completed today will be shown
def completed=(boolean)
self.completed_at = boolean ? Time.current : nil
end
def completed
completed_at && completed_at >= Time.current.beginning_of_day
end
please change your scope to this, it will search time zone specifically.
scope :incomplete, -> {where("completed_at is null OR completed_at < ?", Time.zone.now.beginning_of_day)}
Place this code snippet into your application.rb
config.time_zone = 'Eastern Time (US & Canada)'
and run this so it will be set on heroku heroku config:add TZ=America/Los_Angeles.
Generally, you can also use #in_time_zone. Always use the time like Time.zone.now.
Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
DateTime.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
date and time zones
api docs

Ruby / Rails - Change the timezone of a Time, without changing the value

I have a record foo in the database which has :start_time and :timezone attributes.
The :start_time is a Time in UTC - 2001-01-01 14:20:00, for example.
The :timezone is a string - America/New_York, for example.
I want to create a new Time object with the value of :start_time but whose timezone is specified by :timezone. I do not want to load the :start_time and then convert to :timezone, because Rails will be clever and update the time from UTC to be consistent with that timezone.
Currently,
t = foo.start_time
=> 2000-01-01 14:20:00 UTC
t.zone
=> "UTC"
t.in_time_zone("America/New_York")
=> Sat, 01 Jan 2000 09:20:00 EST -05:00
Instead, I want to see
=> Sat, 01 Jan 2000 14:20:00 EST -05:00
ie. I want to do:
t
=> 2000-01-01 14:20:00 UTC
t.zone = "America/New_York"
=> "America/New_York"
t
=> 2000-01-01 14:20:00 EST
Sounds like you want something along the lines of
ActiveSupport::TimeZone.new('America/New_York').local_to_utc(t)
This says convert this local time (using the zone) to utc. If you have Time.zone set then you can of course to
Time.zone.local_to_utc(t)
This won't use the timezone attached to t - it assumes that it's local to the time zone you are converting from.
One edge case to guard against here is DST transitions: the local time you specify may not exist or may be ambiguous.
I've just faced the same problem and here is what I'm going to do:
t = t.asctime.in_time_zone("America/New_York")
Here is the documentation on asctime
If you're using Rails, here is another method along the lines of Eric Walsh's answer:
def set_in_timezone(time, zone)
Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end
You need to add the time offset to your time after you convert it.
The easiest way to do this is:
t = Foo.start_time.in_time_zone("America/New_York")
t -= t.utc_offset
I am not sure why you would want to do this, though it is probably best to actually work with times the way they are built. I guess some background on why you need to shift time and timezones would be helpful.
Actually, I think you need to subtract the offset after you convert it, as in:
1.9.3p194 :042 > utc_time = Time.now.utc
=> 2013-05-29 16:37:36 UTC
1.9.3p194 :043 > local_time = utc_time.in_time_zone('America/New_York')
=> Wed, 29 May 2013 12:37:36 EDT -04:00
1.9.3p194 :044 > desired_time = local_time-local_time.utc_offset
=> Wed, 29 May 2013 16:37:36 EDT -04:00
Depends on where you are going to use this Time.
When your time is an attribute
If time is used as an attribute, you can use the same date_time_attribute gem:
class Task
include DateTimeAttribute
date_time_attribute :due_at
end
task = Task.new
task.due_at_time_zone = 'Moscow'
task.due_at # => Mon, 03 Feb 2013 22:00:00 MSK +04:00
task.due_at_time_zone = 'London'
task.due_at # => Mon, 03 Feb 2013 22:00:00 GMT +00:00
When you set a separate variable
Use the same date_time_attribute gem:
my_date_time = DateTimeAttribute::Container.new(Time.zone.now)
my_date_time.date_time # => 2001-02-03 22:00:00 KRAT +0700
my_date_time.time_zone = 'Moscow'
my_date_time.date_time # => 2001-02-03 22:00:00 MSK +0400
Here's another version that worked better for me than the current answers:
now = Time.now
# => 2020-04-15 12:07:10 +0200
now.strftime("%F %T.%N").in_time_zone("Europe/London")
# => Wed, 15 Apr 2020 12:07:10 BST +01:00
It carries over nanoseconds using "%N". If you desire another precision, see this strftime reference.
The question's about Rails but it seems, like me, not everyone here is on the ActiveSupport train, so yet another option:
irb(main):001:0> require "time"
=> true
irb(main):003:0> require "tzinfo"
=> true
irb(main):004:0> t = Time.parse("2000-01-01 14:20:00 UTC")
=> 2000-01-01 14:20:00 UTC
irb(main):005:0> tz = TZInfo::Timezone.get("America/New_York")
=> #<TZInfo::DataTimezone: America/New_York>
irb(main):008:0> utc = tz.local_to_utc(t)
=> 2000-01-01 19:20:00 UTC
irb(main):009:0> tz.utc_to_local(utc)
=> 2000-01-01 14:20:00 -0500
irb(main):010:0>
local_to_utc not doing the opposite of utc_to_local might look like a bug but it is at least documented: https://github.com/tzinfo/tzinfo says:
The offset of the time is ignored - it is treated as if it were a local time for the time zone
I managed to do this by calling change with the desired time zone:
>> t = Time.current.in_time_zone('America/New_York')
=> Mon, 08 Aug 2022 12:04:36.934007000 EDT -04:00
>> t.change(zone: 'Etc/UTC')
=> Mon, 08 Aug 2022 12:04:36.934007000 UTC +00:00
https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-change
def relative_time_in_time_zone(time, zone)
DateTime.parse(time.strftime("%d %b %Y %H:%M:%S #{time.in_time_zone(zone).formatted_offset}"))
end
Quick little function I came up with to solve the job. If someone has a more efficient way of doing this please post it!
I spent significant time struggling with TimeZones as well, and after tinkering with Ruby 1.9.3 realized that you don't need to convert to a named timezone symbol before converting:
my_time = Time.now
west_coast_time = my_time.in_time_zone(-8) # Pacific Standard Time
east_coast_time = my_time.in_time_zone(-5) # Eastern Standard Time
What this implies is that you can focus on getting the appropriate time setup first in the region you want, the way you would think about it (at least in my head I partition it this way), and then convert at the end to the zone you want to verify your business logic with.
This also works for Ruby 2.3.1.
I have created few helper methods one of which just does the same thing as is asked by the original author of the post at Ruby / Rails - Change the timezone of a Time, without changing the value.
Also I have documented few peculiarities I observed and also these helpers contains methods to completely ignore automatic day-light savings applicable while time-conversions which is not available out-of-the-box in Rails framework:
def utc_offset_of_given_time(time, ignore_dst: false)
# Correcting the utc_offset below
utc_offset = time.utc_offset
if !!ignore_dst && time.dst?
utc_offset_ignoring_dst = utc_offset - 3600 # 3600 seconds = 1 hour
utc_offset = utc_offset_ignoring_dst
end
utc_offset
end
def utc_offset_of_given_time_ignoring_dst(time)
utc_offset_of_given_time(time, ignore_dst: true)
end
def change_offset_in_given_time_to_given_utc_offset(time, utc_offset)
formatted_utc_offset = ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, false)
# change method accepts :offset option only on DateTime instances.
# and also offset option works only when given formatted utc_offset
# like -0500. If giving it number of seconds like -18000 it is not
# taken into account. This is not mentioned clearly in the documentation
# , though.
# Hence the conversion to DateTime instance first using to_datetime.
datetime_with_changed_offset = time.to_datetime.change(offset: formatted_utc_offset)
Time.parse(datetime_with_changed_offset.to_s)
end
def ignore_dst_in_given_time(time)
return time unless time.dst?
utc_offset = time.utc_offset
if utc_offset < 0
dst_ignored_time = time - 1.hour
elsif utc_offset > 0
dst_ignored_time = time + 1.hour
end
utc_offset_ignoring_dst = utc_offset_of_given_time_ignoring_dst(time)
dst_ignored_time_with_corrected_offset =
change_offset_in_given_time_to_given_utc_offset(dst_ignored_time, utc_offset_ignoring_dst)
# A special case for time in timezones observing DST and which are
# ahead of UTC. For e.g. Tehran city whose timezone is Iran Standard Time
# and which observes DST and which is UTC +03:30. But when DST is active
# it becomes UTC +04:30. Thus when a IRDT (Iran Daylight Saving Time)
# is given to this method say '05-04-2016 4:00pm' then this will convert
# it to '05-04-2016 5:00pm' and update its offset to +0330 which is incorrect.
# The updated UTC offset is correct but the hour should retain as 4.
if utc_offset > 0
dst_ignored_time_with_corrected_offset -= 1.hour
end
dst_ignored_time_with_corrected_offset
end
Examples which can be tried on rails console or a ruby script after wrapping the above methods in a class or module:
dd1 = '05-04-2016 4:00pm'
dd2 = '07-11-2016 4:00pm'
utc_zone = ActiveSupport::TimeZone['UTC']
est_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
tehran_zone = ActiveSupport::TimeZone['Tehran']
utc_dd1 = utc_zone.parse(dd1)
est_dd1 = est_zone.parse(dd1)
tehran_dd1 = tehran_zone.parse(dd1)
utc_dd1.dst?
est_dd1.dst?
tehran_dd1.dst?
ignore_dst = true
utc_to_est_time = utc_dd1.in_time_zone(est_zone.name)
if utc_to_est_time.dst? && !!ignore_dst
utc_to_est_time = ignore_dst_in_given_time(utc_to_est_time)
end
puts utc_to_est_time
Hope this helps.
This worked well for me
date = '23/11/2020'
time = '08:00'
h, m = time.split(':')
timezone = 'Europe/London'
date.to_datetime.in_time_zone(timezone).change(hour: h, min: m)
This changes the timezone to 'EST' without changing the time:
time = DateTime.current
Time.find_zone("EST").local(
time.year,
time.month,
time.day,
time.hour,
time.min,
time.sec,
)

Rails timezone activerecord

My application.rb:
config.time_zone = 'Moscow'
config.time_zone = "(GMT+04:00) Moscow"
config.active_record.default_timezone = 'Moscow'
config.active_record.default_timezone = :local
When i run this commands, i get:
1.9.3-p362 :001 > Time.now
=> 2013-02-14 14:18:42 +0400
1.9.3-p362 :002 > Time.zone.now
=> Thu, 14 Feb 2013 10:18:52 UTC +00:00
So in db i see 10:18:52 UTC +00:00.
But what and how to configure, to see such time, as in Time.Now? (when i insert new row to db i must see time, as given by Time.now, what to configure?)
Server's time is Moscow....
also db is mysql
You can use local time like this:
> current_time = Time.now.utc
=> 2013-02-14 15:15:58 UTC
> current_time .localtime
=> 2013-02-14 10:15:58 -05 hours
You can use also:
Time.now.utc.in_time_zone("Moscow")
But for the answer to your question, the solution could be to set it like this:
config.time_zone = 'Moscow' (don't set this - delete this line)
config.active_record.default_timezone = :local
And also you can use your local time something like this:
Time.local_time
I hope some of this approaches might help you
In your application.rb include these lines:
config.time_zone = 'Moscow'
config.active_record.default_timezone = 'Moscow'
Now that you have set the time zone:
Time.now
=> 2013-02-14 11:14:46 +0000
Time.current
=> Thu, 14 Feb 2013 15:14:48 MSK +04:00
user = User.create(name: 'test')
=> #<User id: 6, name: "test", created_at: "2013-02-14 11:15:00", updated_at: "2013-02-14 11:15:00">
user.created_at
=> Thu, 14 Feb 2013 15:15:00 MSK +04:00
As you can see, despite the fact that rails stores the time as UCT +0000 (very clever btw), the rails interface is providing the required offset. If you call any object, such as user.created_at it will offset the database time to return the zone time.
It is very useful, because it allows the configuration of different time zones for different users. The time will be stored without offset, but each user will get its equivalent time zone.
Time.now return +00:00, doesn't matter your time zone.
Time.current is the same as Time.zone.now.

How to get time/date string to UTC from local time zone

I have this string:
"2011-12-05 17:00:00"
where this is local time
irb(main):034:0> Time.zone
=> (GMT-08:00) Pacific Time (US & Canada)
Now how to I get a Time object with this in UTC?
These don't work:
Time.local("2011-12-05 17:00:00") => 2011-01-01 00:00:00 +0000
Time.local("2011-12-05 17:00:00").utc => 2011-01-01 00:00:00 UTC
UPDATE:
On my local machine, this works:
Time.parse("2011-12-05 17:00:00").utc
=> 2011-12-06 01:00:00 UTC
but on heroku console it doesn't:
Time.parse("2011-12-05 17:00:00").utc
=> 2011-12-05 17:00:00 UTC
It seems that you should use Time.parse instead of Time.local:
require "time"
Time.parse("2011-12-05 17:00:00").utc
Try this in rails application:
Time.zone = "Pacific Time" # => "Pacific Time"
Time.zone.parse("2011-12-05 17:00:00") # => Mon, 05 Dec 2011 17:00:00 UTC +00:00
Details here
I realized my issue was a little more complicated. I wanted to store the times in the database with no time zone, and no time zone conversions. So I left the server and rails app time zone at UTC.
But when I pulled the times out, I would sometimes need to compare them with the current time in the time-zone that the user was in. So I needed to get Time.zone.now for the user, but again, I have Time.zone set to UTC so no automatic conversions happened.
My solution was to pull out the stored user time zone and apply it to Time.now to get the users local time like this:
class Business < ActiveRecord::Base
def tz
TZInfo::Timezone.get(ActiveSupport::TimeZone::MAPPING[self.time_zone])
end
def now
tz.utc_to_local(Time.zone.now)
end
end
So that I could do something like:
#client.appointments.confirmed_in_future(current_business.now)
-
def confirmed_in_future(date_start = Time.zone.now)
confirmed.where("time >= ?", date_start)
end

Override what Ruby thinks is the current time in Time.now?

I want to create test data for an application, and there are a lot of time_at attributes being tracked, too many to override in a maintainable way. What I'm thinking is, can I just change the base reference time variable in Ruby?
This would make it so created_at, updated_at, last_login_at, etc., could be set to an artificial time, so I could do this in tests:
Date.today #=> Thu, 30 Dec 2010
Time.system_time_offset = 1.week.ago # made up
Date.today #=> Thu, 23 Dec 2010
Time.now #=> Thu Dec 23 14:08:38 -0600 2010
user_1 = User.create!
user_1.created_at #=> Thu Dec 23 14:08:38 -0600 2010
Time.reset_system_time # made up
user_2 = User.create!
user_1.created_at #=> Thu Dec 30 14:08:38 -0600 2010
Is there a way to do this?
You could use Mocha to change the return value of Time.now during a test:
Time.stubs(:now).returns(Time.now - 1.day)
A good gem for this is Timecop: https://github.com/travisjeffery/timecop.
You can freeze time or change the time (while it continues to progress) very easily.
Ex.
Time.now
# => 2014-03-14 13:17:02 -0400
Timecop.travel 2.hours.ago
Time.now
# => 2014-03-14 11:17:04 -0400
Its nicer than the mocha solution since all time functions will be affected equally, so you won't have a test where Time.now is returning something different then DateTime.now
Its also more up-to-date than the time-warp gem suggested in another answer.
I use the timewarp gem for this sort of thing. You just put your code in a pretend_now_is(time) block and the code inside will be executed as if that was the actual time.
http://github.com/harvesthq/time-warp
Here's an example
def test_should_find_company_needing_reminded_today
pretend_now_is(Time.utc(2008,"jul",24,20)) do #=> Thu Jul 24 20:00:00 UTC 2008
#company.reminder_day = 'Thursday'
#company.save
companies = Company.find_companies_needing_reminded_today
assert_equal true, companies.include?(#company)
end
end
Honestly, I usually write tests for current time to check if the timestamp is within a reasonable range. i.e., check if the timestamp is greater than 1.minute.ago. Changing the system clock is likely to have all kinds of unpredictable side-effects, so you don't want to do that. You might be able to track down all the places in Ruby where the current time is accessed (though I think most methods just use Time.now) and monkey-patch them for the tests, but I'd probably still prefer just checking the timestamp is within a sane range.
It's also possible to (yuck) monkeypatch Time:
$start = Time.now - 86400 # this time yesterday
class Time
class << Time
def new
$start
end
def now
Time.new
end
end
end
puts(Time.now)
puts($start)

Resources