Ruby time zone handling when merging data - ruby

Two sets of data need to be merged to create a new DateTime object
def mergeDateTime(date_to_merge, time_to_merge)
DateTime.new(date_to_merge.year, date_to_merge.month,date_to_merge.day, time_to_merge.hour, time_to_merge.min, time_to_merge.sec)
end
querying #signature = Signature.where('playtime_id = ?', 514).first
returns
Signature id: 834,[...], created_at: "2017-06-27 05:16:52"
and querying #interruption = Interruption.where('playtime_id = ?', 514).first
returns
Interruption id: 190, [...], pause: "2017-06-27 06:46:19"
but running
mergeDateTime(#signature.created_at, #interruption.pause)
returns
Tue, 27 Jun 2017 08:46:19 +0000
which is wrong as it interpreted the data at GMT +2 and should have generated
Tue, 27 Jun 2017 06:46:19 +0000
How can this presumption of timezone adjustment be neutered? and take into account the offset of the timezone according to the date_to_merge date?

Specify the timezone with in_time_zone:
def mergeDateTime(date_to_merge, time_to_merge)
date_to_merge = date_to_merge.in_time_zone('UTC')
time_to_merge = time_to_merge.in_time_zone('UTC')
DateTime.new(
date_to_merge.year,
date_to_merge.month,date_to_merge.day,
time_to_merge.hour,
time_to_merge.min,
time_to_merge.sec
)
end

While the other answer points in the right direction, the moment of the conversion to UTC matters
ts_pause_raw = #interruption.pause.in_time_zone('UTC')
ts_pause = Time.parse(ts_pause_raw.to_s[11,8])
Then def mergeDateTime(date_to_merge, time_to_merge)can be called without further invocation of in_time_zone.
Occurring after, it is too late.

Related

Convert Datetime with arbitrary time zone to a corresonding Time object, keeping the zone, in ruby 1.8.6

I'm using ruby 1.8.6 on an old server.
I have a DateTime object which is created like this:
dt = DateTime.new(2021, 8, 25, 3, 30, 0).change(:offset => "+0200")
=> Wed, 25 Aug 2021 03:30:00 +0200
That's fine as is. However, I want it to be a Time object instead of a DateTime and I can't figure out how. If I call ".to_time" it just stays as a DateTime object. I can do this:
>> Time.parse(dt.to_s)
=> Wed Aug 25 02:30:00 +0100 2021
and that is showing the time in my local time zone, but I want it to be shown in the +0200 timezone, with that zone stored in the Time object.
Is this possible?

Ruby Comparing Time objects considers date. How to avoid that?

I have a model with a Time attribute
create_table "opened_intervals", force: :cascade do |t|
t.time "start"
t.time "end"
An example of a value would be:
>> oi.start
=> Sat, 01 Jan 2000 06:26:00 UTC +00:00
(I am living in Germany)
If I use the current time, I get following value:
current_time = Time.now
>> current_time
=> 2019-02-14 18:36:12 +0100
If I compare the class of both objects, I have
>> current_time.class
=> Time
>> oi.start.class
=> ActiveSupport::TimeWithZone
In order to make both instances same class, I change the Time class with the .zone method
>> Time.zone.now
=> Thu, 14 Feb 2019 17:38:48 UTC +00:00
>> Time.zone.now.class
=> ActiveSupport::TimeWithZone
Now both instances have same Class.
If I compare them, I get wrong results because of the date:
>> oi.start
=> Sat, 01 Jan 2000 06:26:00 UTC +00:00
>> current_time
=> Thu, 14 Feb 2019 17:40:13 UTC +00:00
>> current_time < oi.end
=> false
I thought about creating a new Time with the hours, minutes and seconds, but then I have the same problem, Ruby always appends a Date.
Of course I could extract the hour, the minutes, the time, create an integer and compare them, but it feels too much.
How can I deal with this issue the Ruby way?
The Time object is always a time with a date? Is there no other way to achieve a Time just with the time?
What would be the best approach for this problem?
I solved it this way:
def to_integer(time)
hours = time.hour < 10 ? "0#{time.hour}" : time.hour
minutes = time.min < 10 ? "0#{time.min}" : time.min
seconds = time.sec < 10 ? "0#{time.sec}" : time.sec
"#{hours}#{minutes}#{seconds}"
end
It feels too much (I could refactor it, but still too much)
to_integer(current_time) < to_integer(oi.end)
You can just modulus the days off to get the remaining time value.
t1 = Time.now
t2 = t2 = Time.at(Time.now.to_i - (5*24*3600)) # later time in day but previous date
tod1 = t.to_i % (24*3600)
=> 69734
tod2 = t2.to_i % (24*3600)
=> 69912
we can clearly see that t2 is a later time of day or clock time if you will and the modulus operation is very clear if you know anything about the unix epoch.

Cant get active_record.time_zone_aware_attributes & skip_time_zone_conversion_for_attributes

I cant seem to get skip_time_zone_conversion_for_attributes or active_record.time_zone_aware_attributes working in my Rails 5 App
in my application.rb I have
config.active_record.default_timezone = :local
config.active_record.time_zone_aware_attributes = false
And in my model I have
self.skip_time_zone_conversion_for_attributes = [:start_date, :end_date]
Yet when the stat date and end date are sent back to the client as JSON, they are always converted into the clients timezone. I dont want that as I need to show the client the server time.
Why is this not working? Do I need to do something else to ensure that the start_date and end_date in my model, do not get translated to the users time zone ?
Thanks
This is occurred due to rails have several pitfalls. Most importantly because Time.now and Time.current are completely different things.
config.time_zone = 'Berlin' # Your local time zone
config.active_record.default_timezone = :local
config.active_record.time_zone_aware_attributes = false
This are fields present within application.rb, make things happen for all your model's time related attributes.
For example -
if you didn't mention any time_zone then it will take 'UTC' by defaults
post = Post.create
post.created_at # => Mon, 22 Apr 2019 06:43:08 UTC +00:00
Time.current # => Mon, 22 Apr 2019 06:43:35 UTC +00:00
Time.now # => 2019-04-22 12:13:40 +0530
In your database and Time.current shows the 'UTC' format but Time.now is picking your local timezone.
Now if you change time_zone,
config.time_zone = 'Berlin'
Then,
post = Post.create
post.created_at # => Mon, 22 Apr 2019 08:45:48 CEST +02:00
Time.current # => Mon, 22 Apr 2019 08:46:05 CEST +02:00
Time.now # => 2019-04-22 12:16:00 +0530
Now in your database, created_at value stored with 'UTC' time zone but when you retrieve that field it shows in set time_zone format means in 'Berlin'. But still your Time.now is in your local timezone.
Now as per your question I am using skip_time_zone_conversion_for_attributes, and changed my model with time_zone is 'Berlin'
class Post < ActiveRecord::Base
self.skip_time_zone_conversion_for_attributes = [:created_at]
end
Then,
post = Post.create
post.created_at # => 2019-04-22 06:56:31 UTC
Time.current # => Mon, 22 Apr 2019 08:56:53 CEST +02:00
Time.now # => 2019-04-22 12:26:46 +0530
As per skip_time_zone_conversion_for_attributes method it's skip for that attributes, when I retried the attribute - created_at it shows it in 'UTC' format instead of my set time_zone 'Berlin', working as expected.
So this could be helpful for you to understand and check your setting before set it in JSON.
For more information you can refer this link -
https://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
https://makandracards.com/makandra/46009-working-with-or-without-time-zones-in-rails-applications

Ruby: combine Date and Time objects into a DateTime

Simple question, but I can't find a good or definitive answer. What is the best and most efficient way to combine Ruby Date and Time objects (objects, not strings) into a single DateTime object?
I found this, but it's not as elegant you would hope:
d = Date.new(2012, 8, 29)
t = Time.now
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec, t.zone)
By the way, the ruby Time object also stores a year, month, and day, so you would be throwing that away when you create the DateTime.
When using seconds_since_midnight, changes in daylight savings time can lead to unexpected results.
Time.zone = 'America/Chicago'
t = Time.zone.parse('07:00').seconds_since_midnight.seconds
d1 = Time.zone.parse('2016-11-06').to_date # Fall back
d2 = Time.zone.parse('2016-11-07').to_date # Normal day
d3 = Time.zone.parse('2017-03-12').to_date # Spring forward
d1 + t
#=> Sun, 06 Nov 2016 06:00:00 CST -06:00
d2 + t
#=> Mon, 07 Nov 2016 07:00:00 CST -06:00
d3 + t
#=> Sun, 12 Mar 2017 08:00:00 CDT -05:00
Here's an alternative, similar to #selva-raj's answer above, using string interpolation, strftime, and parse. %F is equal to %Y-%m-%d and %T is equal to %H:%M:%S.
Time.zone = 'America/Chicago'
t = Time.zone.parse('07:00')
d1 = Time.zone.parse('2016-11-06').to_date # Fall back
d2 = Time.zone.parse('2016-11-07').to_date # Normal day
d3 = Time.zone.parse('2017-03-12').to_date # Spring forward
Time.zone.parse("#{d1.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 06 Nov 2016 07:00:00 CST -06:00
Time.zone.parse("#{d2.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 07 Nov 2016 07:00:00 CST -06:00
Time.zone.parse("#{d3.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 12 Mar 2017 07:00:00 CDT -05:00
Simple:
Date.new(2015, 2, 10).to_datetime + Time.parse("16:30").seconds_since_midnight.seconds
# => Object: Tue, 10 Feb 2015 16:30:00 +0000
You gotta love Ruby!
If using Rails, try any of these:
d = Date.new(2014, 3, 1)
t = Time.parse("16:30")
dt = d + t.seconds_since_midnight.seconds
# => ActiveSupport::TimeWithZone
dt = (d + t.seconds_since_midnight.seconds).to_datetime
# => DateTime
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec)
# => DateTime
If you are using Ruby on Rails, this works great.
I built a method to extend the DateTime class to combine a date and a time. It takes the zone from the date so that it does not end up an hour off with daylight savings time.
Also, for convenience, I like being able to pass in strings as well.
class DateTime
def self.combine(d, t)
# pass in a date and time or strings
d = Date.parse(d) if d.is_a? String
t = Time.zone.parse(t) if t.is_a? String
# + 12 hours to make sure we are in the right zone
# (eg. PST and PDT switch at 2am)
zone = (Time.zone.parse(d.strftime("%Y-%m-%d")) + 12.hours ).zone
new(d.year, d.month, d.day, t.hour, t.min, t.sec, zone)
end
end
So you can do:
DateTime.combine(3.weeks.ago, "9am")
or
DateTime.combine("2015-3-26", Time.current)
etc...
I found another way, I hope this is correct.
datetojoin=Time.parse(datetime).strftime("%Y-%m-%d")
timetojoin=Time.parse(time).strftime("%T")
joined_datetime = Time.parse(datetojoin +" "+ timetojoin).strftime("%F %T")
Any thoughts? Please share.

How to save a timezone correctly with Ruby and MongoId?

Please excuse me if this is a bit of a noob issue:
I have an app where users can set their own Timezones in their profile.
When someone adds a Lineup (app specific terminology), I do the following:
time = ActiveSupport::TimeZone.new(user.timezone).parse(
"Wednesday, 26 October, 2011 13:30:00"
)
# This outputs: 2011-10-26 13:30:00 +0200 - valid according to the user selected TZ
I then save the Lineup:
Lineup.create({
:date => time.gmtime,
:uid => user._id,
:pid => product._id
})
This should (in theory) save the date as gmtime, but I get the following when viewing the record:
{
"_id": ObjectId("4e9c6613e673454f93000002"),
"date": "Wed, 26 Oct 2011 13: 30: 00 +0200",
"uid": "4e9b81f6e673454c8a000001",
"pid": "4e9c6613e673454f93000001",
"created_at": "Mon, 17 Oct 2011 19: 29: 55 +0200"
}
As you can see the date field is wrong - it still maintaining the user timezone, it should be GMT, not timezone specific.
If I output time.gmtime, I get the right time (that should be saved):
2011-10-26 11:30:00 UTC (correct)
Any ideas how to save the GMT date so that it actually saves the GMT date?
It looks like you need to specify the field type of your date attribute. I would use a Time field if you want mongoid to handle the zones properly.
class Lineup
include Mongoid::Document
field :date, type: Time
end
You will also probably want to set the following in config/mongoid.yml
defaults: &defaults
use_utc: false
use_activesupport_time_zone: true
This sounds counterintuitive, but this is the current way to make mongoid use UTC as the default timezone.
Finally, have a look at the mongoid-metastamp gem. It will give you much better support for querying across multiple timezones, while still seamlessly working like a native Time field.

Resources