How would I get a UNIX timestamp (number of seconds since 1970 GMT) from a Date object in a Rails app?
I know Time#to_i returns a timestamp, but doing Date#to_time and then getting the timestamp results in something that's off by about a month (not sure why...).
Any help is appreciated, thanks!
Edit: OK, I think I figured it out- I was processing a date several times in a loop, and each time the date was moved a little because of a time zone mismatch, ultimately leading to my timestamp being a month off. Still, I'd be interested in knowing if there's any way to do this without relying on Date#to_time.
The code date.to_time.to_i should work fine. The Rails console session below shows an example:
>> Date.new(2009,11,26).to_time
=> Thu Nov 26 00:00:00 -0800 2009
>> Date.new(2009,11,26).to_time.to_i
=> 1259222400
>> Time.at(1259222400)
=> Thu Nov 26 00:00:00 -0800 2009
Note that the intermediate DateTime object is in local time, so the timestamp might be several hours off from what you expect. If you want to work in UTC time, you can use DateTime's method "utc".
I get the following when I try it:
>> Date.today.to_time.to_i
=> 1259244000
>> Time.now.to_i
=> 1259275709
The difference between these two numbers is due to the fact that Date does not store the hours, minutes or seconds of the current time. Converting a Date to a Time will result in that day, midnight.
Solution for Ruby 1.8 when you have an arbitrary DateTime object:
1.8.7-p374 :001 > require 'date'
=> true
1.8.7-p374 :002 > DateTime.new(2012, 1, 15).strftime('%s')
=> "1326585600"
The suggested options of using to_utc or utc to fix the local time offset does not work. For me I found using Time.utc() worked correctly and the code involves less steps:
> Time.utc(2016, 12, 25).to_i
=> 1482624000 # correct
vs
> Date.new(2016, 12, 25).to_time.utc.to_i
=> 1482584400 # incorrect
Here is what happens when you call utc after using Date....
> Date.new(2016, 12, 25).to_time
=> 2016-12-25 00:00:00 +1100 # This will use your system's time offset
> Date.new(2016, 12, 25).to_time.utc
=> 2016-12-24 13:00:00 UTC
...so clearly calling to_i is going to give the wrong timestamp.
DateTime.new(2012, 1, 15).to_time.to_i
Related
I am a little bit confused by the Date.yesterday(), for example:
$ rails c
Loading development environment (Rails 6.0.3.2)
irb(main):001:0> Date.today
=> Fri, 10 Jul 2020
irb(main):002:0> Date.yesterday
=> Wed, 08 Jul 2020
irb(main):003:0> Time.now
=> 2020-07-10 03:54:46.02207138 +0530
irb(main):004:0>
But if I am not wrong, if today is Friday, which is true, the previous day should be Thursday as I learnt in my primary school..
What's going on in here?
tl;dr: Use Date.current and Time.current instead of Date.today and Time.now.
Your application has its own time zone, Time.zone. Ruby is not aware of time zones, but Rails is. Rails partially updates Time and Date to be aware of time zones, but not completely. Any methods Rails adds will be time zone aware. Date.yesterday and Date.tomorrow, for example. Built-in Ruby methods it leaves alone, like Date.today. This causes some confusion.
Date.today is giving today according to your local time zone, +0530. Date.yesterday is giving yesterday according to your application's time zone which I'm guessing is +0000 (UTC). 2020-07-10 03:54:46 +0530 is 2020-07-09 22:24:46 UTC so Date.yesterday is 2020-07-08.
Use Date.current instead of Date.today. Date.yesterday is a thin wrapper around Date.current.yesterday. Similarly, use Time.current instead of Time.now.
The ThoughtBot article It's About Time (Zones) discusses Rails time zones in detail and has simple DOs and DON'Ts to avoid time zone confusion.
DON'T USE
Time.now
Date.today
Date.today.to_time
Time.parse("2015-07-04 17:05:37")
Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z")
DO USE
Time.current
2.hours.ago
Time.zone.today
Date.current
1.day.from_now
Time.zone.parse("2015-07-04 17:05:37")
Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z").in_time_zone
Make sure to check the location of the server where the application is running. The value of Date.yesterday will likely depend on the location of the server with respect to the international date line. For example, while it is still Thursday, 07/09/2020 in New York City, it is Friday, 07/10/2020 in New Zealand.
In your Rails app, you may have a value set for Time.zone or in the configuration for config.time_zone.
If one of these values is configured, then Rails uses this as follows:
def yesterday
::Date.current.yesterday
end
def current
::Time.zone ? ::Time.zone.today : ::Date.today
end
These are ActiveSupport Date helpers
So it's indeed a result of wrong Timezone, in my case Time.zone.name returned "UTC". The correct zone for me is Asia/Kolkata, to check the presence of the timezone:
ActiveSupport::TimeZone::MAPPING.select { |x| x[/^kol/i] }
Which returned {"Kolkata"=>"Asia/Kolkata"} in my case.
For it to take effect temporarily, I set Time.zone = 'Kolkata' in the rails console.
For a permanent effect, added config.time_zone = 'Kolkata' to {project_root}/config/application.rb.
This fixed the problem, and Date.yesterday(), Date.today(), and Date.tomorrow() are working as expected.
I added this line of code
self.auth_history.push [start_date, self.coupon_code]
And got this error message
Date is not currently supported; use a UTC Time instance instead.
I also tried start_date.utc, but it didn't work either.
Please help. Thanks.
I got this answer from Seattle Brigade group -
===
I didn't see start_date defined in your code as a key in MongoMapper, so
I'll assume you're creating your own date object, either directly via Ruby,
or wrapped by Rails. As far as I know, and someone please correct me, Mongo
stores dates as UTC time in milliseconds since epoch. So when you define a
key with a :date mapping in MongoMapper, you're wrapping a Time object in
Ruby.
Therefore, if you want to store a date inside of Mongo, and it wasn't
created by MongoMapper, make sure you create a Time object in UTC.
MongoMapper comes with a Date mixin method called to_mongo that you can use.
>> Time.now.utc
=> Fri Jan 28 03:47:50 UTC 2011
>> require 'date'
=> true
>> date = Date.today
=> #<Date: 4911179/2,0,2299161>
>> Time.utc(date.year, date.month, date.day)
=> Thu Jan 27 00:00:00 UTC 2011
>> require 'rubygems'
=> true
>> require 'mongo_mapper'
=> true
>> Date.to_mongo(date)
=> Thu Jan 27 00:00:00 UTC 2011
But watch out for the time change.
>> Date.to_mongo(Time.now)
=> Thu Jan 27 00:00:00 UTC 2011
>> Date.to_mongo(Time.now.utc)
=> Fri Jan 28 00:00:00 UTC 2011
Good luck.
===
And by using
Date.to_mongo(start_date)
it works for me.
First, I think the question title is bad in description. Actually, the difference between different timezone is on Time not on Date. So, it's really not proper to say I want to convert a date to UTC format.
Here is another way in Ruby to convert DateTime to its UTC format:
DateTime.now.new_offset(0)
Here's another option:
Time.at(Date.today.to_datetime.to_i).utc
Here I am using Date.today as an arbitrary example date. Replace with whatever date you want to convert. Once the date is converted to a Time instance, it can be serialized to BSON without any problem, as Time is a supported primitive type, that is to say, it can be saved using MongoMapper to the database.
As per EfratBlaier's comment I have updated the answer.
Date.today.to_time.utc
I'm a bit confused about timezones in rails. I want my rails app to use British Summer Time (like daylight savings in the US) for the timestamps set in updated_at and created_at in my models. I changed my environment.rb to say
config.time_zone = 'London'
The ubuntu server my app is on seems to use BST for it's time: in the command line, for example, if i type 'date' i get the current time (not offset by an hour). In the rails console, i see the following:
>> time = Time.now
=> Wed Oct 27 16:29:17 +0100 2010
>> time.zone
=> "BST"
All fine. However, if i make a new AR model object and save it, the timestamps are from an hour ago. So, it looks like this is using UTC. Now, i can see the logic in this: since the timestamps might be used in the model logic, you want them to be based on an unvarying yardstick time, ie UTC. But, this is a weird bit of behaviour that i don't understand:
#change a record and save it
>> someobj.save
=> true
#object's updated_at is one hour ago
>> someobj.updated_at
=> Wed, 27 Oct 2010 15:34:22 UTC +00:00
>> Time.now
=> Wed Oct 27 16:34:31 +0100 2010
#however, Time.now - object's updated at is just a few seconds.
>> Time.now - someobj.updated_at
=> 15.305549
So, before doing the subtraction, updated_at is converted into the current time zone.
The reason i want to show the date in the current time zone is just for status reports etc in the views: if someone updates something i want them to see that it was updated '1 minute ago' not 'one hour ago'.
Can anyone unconfuse me? cheers, max
EDIT: My immediate problem, of showing the right time in the status, is solved by using the 'time_ago_in_words' helper, which adjusts for time zone. I'd still like someone to explain what's going on with the timestamps though :)
Timestamps are stored in UTC by default, and this is probably the best way to do it. If you move from one server environment to another, you don't want all of your times shifting around just because you switched time zones.
If you want to know what the timestamp is in your local time zone, you just have to ask for it that way:
someobj.updated_at.localtime
Note the offset listed at the end of the times -- the first offset is 0, the second is 1. When the time calculation occurs, the offset is included automatically, so that the subtraction gives you the correct result. someobj.updated_at and Time.now each displays its value in a different time zone, so they are really only 9 seconds apart, not 1 hour and 9 seconds.
> e = Event.first
> e.registration_start_utc #registration_start_utc is a datetime column
=> Sat, 23 Oct 2010 06:38:00 UTC +00:00
> e.registration_start_utc.utc?
=> true
> ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(e.registration_start_utc)
=> Sat, 23 Oct 2010 02:38:00 UTC +00:00
2 questions about this:
1) Why is that last output showing "UTC" -- the hour got converted (6 => 2) but it still says UTC. Why not EST/EDT?
2) What happens after daylight savings time switches over and the offset for New York moves from -4 to -5? The value in the DB doesn't change so my only conclusion is that my app will start showing "1:38" everywhere instead of the correct 2:38?
I'm mostly concerned with #2 here. #1 is more of a curiosity.
Thanks!
2) utc_to_local uses the date to determine which offset is correct, so the output will always be the same for a given date.
You can test for that like this:
t = Time.utc(2011,3, 14, 12)
# => 2011-03-14 12:00:00 UTC
t2 = Time.utc(2011,3, 11, 12)
# => 2011-03-11 12:00:00 UTC
ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(t)
# => 2011-03-14 08:00:00 UTC
ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(t2)
# => 2011-03-14 07:00:00 UTC
1) It doesn't seem right to me either. My guess is that they are interested only in the actual value of the hour, minutes, etc... and disregard the timezone.
In any case, you might be better off using:
e.registration_start_utc.in_time_zone("Eastern Time (US & Canada)")
See also this question I just asked...
I have a rails time-based query which has some odd timezone sensitive behaviour, even though as far as I know I'm using UTC. In a nutshell, these queries give different answers:
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours).gmtime]).length
=> 279
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours)]).length
=> 280
Where the DB actually does contain one model created in the last hour, and the total number of models is 280. So only the first query is correct.
However, in environment.rb I have:
config.time_zone = 'UTC'
The system time zone (as reported by 'date') is BST (which is GMT+1) - so somehow this winds up getting treated as UTC and breaking queries.
This is causing me all sorts of problems as I need to parameterise the query passing in different times to an action (which are then converted using Time.parse()), and even though I send in UTC times, this 'off by one hour' DST issue crops a lot. Even using '.gmtime()' doesn't always seem to fix it.
Obviously the difference is caused somehow by an implicit conversion somewhere resulting in BST being incorrectly treated as UTC, but why? Doesn't rails store the timestamps in UTC? Isn't the Time class timezone aware? I am using Rails 2.2.2
So what is going on here - and what is the safe way to program around it?
edit, some additional info to show what the DB and Time class are doing:
>> Model.find(:last).created_at
=> Tue, 11 Aug 2009 20:31:07 UTC +00:00
>> Time.now
=> Tue Aug 11 22:00:18 +0100 2009
>> Time.now.gmtime
=> Tue Aug 11 21:00:22 UTC 2009
The Time class isn't directly aware of your configured timezone. Rails 2.1 added a bunch of timezone support, but Time will still act upon your local timezone. This is why Time.now returns a BST time.
What you likely want is to interact with Time.zone. You can call methods on this like you would the Time class itself but it will return it in the specified time zone.
Time.zone.now # => Tue, 11 Aug 2009 21:31:45 UTC +00:00
Time.zone.parse("2:30 PM Aug 23, 2009") # => Sun, 23 Aug 2009 14:30:00 UTC +00:00
Another thing you have to be careful with is if you ever do queries on the database where you are comparing times, but sure to use the UTC time (even if you have a different time zone specified) because Rails always stores UTC in the database.
Item.all(:conditions => ["published_at <= ?", Time.now.utc])
Also, instead of Time.now-1.hour do 1.hour.ago. It is easier to read and Rails will automatically use the configured timezone.
The TimeZone you need to set is UK, this will automatically handle BST
Time.zone = 'UK'
Time.zone.now
=> Sun, 17 Oct 2010 02:09:54 BST +01:00
start_date_format = DateTime.strptime(#start_date, date_format)
start_date_format_with_hour =
DateTime.strptime((start_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
end_date_format = DateTime.strptime(#end_date, date_format)
end_date_format_with_hour = DateTime.strptime((end_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
#filters_date = "invoices.created_at >= ? AND invoices.created_at < ?", start_date_format_with_hour, end_date_format_with_hour