Understanding Time#utc? - ruby

As I understand, UTC means a time is given in timezone +00:00. But Ruby thinks different in Time#utc?. I've observed this in Ruby 2.5.1:
a = Time.new(2018,6,13, 9,0,0, '+00:00')
# => 2018-06-13 09:00:00 +0000
b = Time.utc(2018,6,13, 9,0,0)
# => 2018-06-13 09:00:00 UTC
a == b
# => true
a.utc?
# => false (WHY???)
b.utc?
# => true
IMHO, a.utc? should return true. Is there any explanation?
Addition: From the Ruby docs for Time#utc?
Returns true if time represents a time in UTC (GMT).
What exactly means "representing a time in UTC/GMT"? An offset of 0 is not enough, obviously.

Implementation-wise, Ruby's (i.e. MRI) internal time structure has a gmt field which specifies the time's type:
PACKED_STRUCT_UNALIGNED(struct time_object {
wideval_t timew; /* time_t value * TIME_SCALE. possibly Rational. */
struct vtm vtm;
uint8_t gmt:3; /* 0:localtime 1:utc 2:fixoff 3:init */
uint8_t tm_got:1;
});
The utc? method merely checks whether gmt is 1.
Therefore, a time instance in local time or a time instance with explicit offset will never be utc?, even if your system's timezone offset is UTC+0:
Time.local(2018) #=> 2018-01-01 00:00:00 +0000
Time.local(2018).utc? #=> false
Time.new(2018) #=> 2018-01-01 00:00:00 +0000
Time.new(2018).utc? #=> false
as opposed to a time instance created via utc: (note that the offset is shown as UTC)
Time.utc(2018) #=> 2018-01-01 00:00:00 UTC
Time.utc(2018).utc? #=> true
You could check the utc_offset instead:
t = Time.new(2018) #=> 2018-01-01 00:00:00 +0000
t.utc_offset #=> 0
t.utc_offset.zero? #=> true

Related

Determine if a past date falls in the DST range using ruby 2.4

In ruby 2.4 there was a change to how the DateTime#to_time method works. These changes have broken some existing code I had which checked if a given past datetime was in DST
The date below is in the DST range for that year. However, ruby is reporting that dst? == false. I'm pretty stumped here: without using rails how can I test the dst? value of a past datetime?
# 2016-07-27 00:00:00
unix_timestamp = 1469577600
time = Time.at(unix_timestamp).utc
pacific_time = Time.new(time.year, time.month, time.day, 0, 0, 0, "-08:00")
=> 2016-07-27 00:00:00 -0800
pacific_time.dst?
=> false
pacific_time.zone
=> nil
If your local timezone is Pacific Time, then let Time assume it:
irb(main):001:0> unix_timestamp = 1469577600
=> 1469577600
time = Time.at(unix_timestamp).utc
irb(main):004:0> pacific_time = Time.local(time.year, time.month, time.day)
=> 2016-07-27 00:00:00 -0700
irb(main):006:0> pacific_time.dst?
=> true
irb(main):007:0> pacific_time.zone
=> "PDT"
You can also get the local version of your UTC time:
irb(main):015:0> time_local = time.getlocal
=> 2016-07-26 17:00:00 -0700
irb(main):016:0> time_local.dst?
=> true
irb(main):017:0> time_local.zone
=> "PDT"
But, it may be a little brittle to depend on the local timezone without actually specifying it. If you need to display times in another timezone, different than UTC and different than the local one, then maybe Rails' in_time_zone and this related question can help.

`DateTime.strptime` returns invalid date for weekday/time string

Why won't Ruby's strptime convert this to a DateTime object:
DateTime.strptime('Monday 10:20:20', '%A %H:%M:%S')
# => ArgumentError: invalid date
While these work?
DateTime.strptime('Wednesday', '%A')
# => #<DateTime: 2015-11-18T00:00:00+00:00 ((2457345j,0s,0n),+0s,2299161j)>
DateTime.strptime('10:20:20', '%H:%M:%S')
# => #<DateTime: 2015-11-18T10:20:20+00:00 ((2457345j,37220s,0n),+0s,2299161j)>
This looks like a bug - minitech's comment is spot on. For now, though, a workaround (because you probably want this to work now):
You can split it on the space, get the date from the weekday, then get the time component from the other string (using the _strptime method minitech mentioned). Then you can set the time on the first date to the time component from the second string:
def datetime_from_weekday_time_string(string)
components = string.split(" ")
date = DateTime.strptime(components[0], '%A')
time = Date._strptime(components[1], '%H:%M:%S') # returns a hash like {:hour=>10, :min=>20, :sec=>20}
return date.change(time)
end
2.2.2 :021 > datetime_from_weekday_time_string("Monday 10:20:20")
=> Mon, 16 Nov 2015 10:20:20 +0000
2.2.2 :022 > datetime_from_weekday_time_string("Saturday 11:45:21")
=> Sat, 21 Nov 2015 11:45:21 +0000
2.2.2 :023 > datetime_from_weekday_time_string("Thursday 23:59:59")
=> Thu, 19 Nov 2015 23:59:59 +0000

Time.parse and DateTime.parse returns different results

Why do these two parse statements return different results?
time = "13:30:0"
DateTime.parse(time).to_time.utc
#=> 2013-10-13 13:30:00 UTC
Time.parse(time).utc
#=> 2013-10-13 11:30:00 UTC
There is no timezone information in the input String. DateTime.parse therefore assumes UTC. Time.parse assumes local time, and I guess you're in UTC+2.
>> time = "13:30:0"
=> "13:30:0"
>> DateTime.parse(time).to_s
=> "2013-10-13T13:30:00+00:00"
>> Time.parse(time).to_s
=> "2013-10-13 13:30:00 +0200"

Ruby timezone offset problem

I need to assign a timezone offset to a Time to get current day of the week for a specified offset.
This is not with rails so I need a pure Ruby formatter/parser to do this.
Thanks.
This is what I found:
require 'date'
local = DateTime.now
new_offset = Rational(0, 24) #put the offset you want as first argument
utc = local.new_offset(new_offset)
Returns the offset in seconds between the timezone of time and UTC.
t = Time.gm(2000,1,1,20,15,1) #=> 2000-01-01 20:15:01 UTC
t.gmt_offset #=> 0
l = t.getlocal #=> 2000-01-01 14:15:01 -0600
l.gmt_offset #=> -21600
#As a string
t = Time.new(2011,6,27,14,10,0, "+07:00")
# or in seconds from UTC
t = Time.new(2011,6,27,14,10,0, 7*60*60)

How do I Convert DateTime.now to UTC in Ruby?

If I have d = DateTime.now, how do I convert 'd' into UTC (with the appropriate date)?
DateTime.now.new_offset(0)
will work in standard Ruby (i.e. without ActiveSupport).
d = DateTime.now.utc
Oops!
That seems to work in Rails, but not vanilla Ruby (and of course that is what the question is asking)
d = Time.now.utc
Does work however.
Is there any reason you need to use DateTime and not Time? Time should include everything you need:
irb(main):016:0> Time.now
=> Thu Apr 16 12:40:44 +0100 2009
Unfortunately, the DateTime class doesn't have the convenience methods available in the Time class to do this. You can convert any DateTime object into UTC like this:
d = DateTime.now
d.new_offset(Rational(0, 24))
You can switch back from UTC to localtime using:
d.new_offset(DateTime.now.offset)
where d is a DateTime object in UTC time. If you'd like these as convenience methods, then you can create them like this:
class DateTime
def localtime
new_offset(DateTime.now.offset)
end
def utc
new_offset(Rational(0, 24))
end
end
You can see this in action in the following irb session:
d = DateTime.now.new_offset(Rational(-4, 24))
=> #<DateTime: 106105391484260677/43200000000,-1/6,2299161>
1.8.7 :185 > d.to_s
=> "2012-08-03T15:42:48-04:00"
1.8.7 :186 > d.localtime.to_s
=> "2012-08-03T12:42:48-07:00"
1.8.7 :187 > d.utc.to_s
=> "2012-08-03T19:42:48+00:00"
As you can see above, the initial DateTime object has a -04:00 offset (Eastern Time). I'm in Pacific Time with a -07:00 offset. Calling localtime as described previously properly converts the DateTime object into local time. Calling utc on the object properly converts it to a UTC offset.
Try this, works in Ruby:
DateTime.now.to_time.utc
You can set an ENV if you want your Time.now and DateTime.now to respond in UTC time.
require 'date'
Time.now #=> 2015-11-30 11:37:14 -0800
DateTime.now.to_s #=> "2015-11-30T11:37:25-08:00"
ENV['TZ'] = 'UTC'
Time.now #=> 2015-11-30 19:37:38 +0000
DateTime.now.to_s #=> "2015-11-30T19:37:36+00:00"
In irb:
>>d = DateTime.now
=> #<DateTime: 11783702280454271/4800000000,5/12,2299161>
>> "#{d.hour.to_i - d.zone.to_i}:#{d.min}:#{d.sec}"
=> "11:16:41"
will convert the time to the utc. But as posted if it is just Time you can use:
Time.now.utc
and get it straight away.

Resources