Ruby - Getting the next Daylight Savings change - ruby-on-rails

I know there is a method to determine if a certain time is on Daylight Savings Time (Time.now.dst?) but is there a method to give us the next date when Daylight Savings will change?
For example, Google returns Sunday, November 1 as the next Daylight Savings Time change in 2015.

Since these are dates that are based on other values, like the timezone you are working with, it requires a module like ActiveSupport/TZInfo.
require 'active_support/core_ext/time/zones'
tz = TZInfo::Timezone.get('US/Pacific')
# pick a timezone to work with
tz.current_period #returns an object of the current period
=> #<TZInfo::TimezonePeriod: #<TZInfo::TimezoneTransitionDefinition:
#<TZInfo::TimeOrDateTime: 1425808800>,#<TZInfo::TimezoneOffset: -28800,3600,PDT>>,
#<TZInfo::TimezoneTransitionDefinition: #<TZInfo::TimeOrDateTime: 1446368400>,
#<TZInfo::TimezoneOffset: -28800,0,PST>>>
tz.current_period.local_start.to_s
# => "2015-03-08T03:00:00+00:00"
tz.current_period.local_end.to_s
# => "2015-11-01T02:00:00+00:00"
One thing I haven't figured out is that since regular Ruby Core does this:
Time.now.dst?
# => true
Where is it getting this info? I found the TZInfo classes through ActiveSupport. Is Ruby just getting a boolean value from the OS?

How about this extension of the Time class:
class Time
class << self
def next_dst_change
startdate = Date.today
enddate = Date.today.end_of_year
match = Date.today.to_time.dst? ? false : true
startdate.upto(enddate).find { |date| date.to_time if date.to_time.dst? == match }
end
end
end
Now you can do Time.next_dst_change. You can apply this on your own timezone only but it solves your problem.

Related

Is created_at in Rails by default Time.now.utc.to_date?

When Rails creates an active record and inserts it, is the created_at value practically the same as Time.now.utc.to_date?
In most cases yes, but it depends on the default timezone configuration option.
ActiveRecord::Timestamp code:
def current_time_from_proper_timezone
default_timezone == :utc ? Time.now.utc : Time.now
end
You can change timezone setting in:
config.active_record.time_zone_aware_attributes = false
If you meant to_date, then no, in the worst case it could be nearly 24 hours off.
If you meant to_datetime, then I believe it will be the same to the second. But note that if you call Time.now immediately before or after creating a record it may not match to the second. I'm curious to know why you need to convert to a DateTime though.
Just test it yourself (let's say your AR class is Post):
dtm_before = Time.now.to_datetime
post = Post.create!(attributes)
dtm_after = Time.now.to_datetime # zone does not matter!
# these differences should be tiny
dtm_before.to_time - post.created_at
dtm_after.to_time - post.created_at
I said the zone doesn't matter because when you're doing time arithmetic, zones are automatically taken into account. Example:
# let's assume your local TZ-offset isn't zero
t = Time.now
t == t.getutc # true
t - t.getutc # 0.0 (these are the exact same instant)

How to know if a date is the previous month or earlier?

I'm looking for some conditions to start billing my clients.
Every time that my client make a contract with me, i initialize a date in the attribute start_billing_at. I'd like to know now, if the start_billing_at attribute has been initialized during the previous month. Hope that my problem is more clear now
Thx for help
EDIT
I'v got to know if my date is between the first and the last day of the previous month
Subtracting two dates and calling to_i on it will give you the difference in days, you can switch on that:
if (Date.today - other_date).to_i < 30
# less than a month
else
# more than a month
end
Of course this doesn't exactly follow the months, but for my use cases it's generally good enough.
Another alternative is:
if date_to_check > Date.today.last_month
# within the last month
end
Or checking for inclusion in the range of last month's dates:
last_month = Date.today.last_month
(last_month.beginning_of_month..last_month.end_of_month).cover?(date_to_check)
%w|2017-07-01 2017-06-01|.map do |d|
(Date.today.month - Date.parse(d).month) % 12 == 1
end
#⇒ [true, false]
Here is my solution, based on Michael kohl solutions
def calcul_bill(date)
if Time.zone.today.last_month.strftime('%b, %Y') == date.strftime('%b, %Y')
#Do actions
else
#Do other actions
end
end
My date format is "Wed, 30 Aug 2017" is this case, so i just compare Month and year
I believe I would go for:
start_billing_at.beginning_of_month == Date.today.last_month.beginning_of_month
With a refinement you could define a method on date that allows you to:
start_billing_at.last_month?
So:
module BillingDateExtensions
refine Date do
def last_month?
self.beginning_of_month == Date.today.last_month.beginning_of_month
end
end
end
... and you can allow this to be mixed in to Date where you need it with:
using BillingDateExtensions

Ignore DST on Rails Timzone

I receive time-stamped data from some equipments, who are configured with a timezone (Europe/Paris, for example).
Problem is that some of them are not aware of DST (Daylight Saving Time) change, and some are, which causes us to have a offset problem when we're in DST (like right now).
Today I have the following code to parse this data:
def parse_timestamp(timestamp, format=nil)
case timestamp
when String then #timezone.local_to_utc(Time.strptime(timestamp, format))
when Integer then #timezone.local_to_utc(#timezone.at(timestamp))
else raise Etl::Parsers::TimeConversionError, "Can't parse timestamp #{timestamp.inspect}"
end
end
The #timezone variable is created like this: ActiveSupport::TimeZone[timezone].
I'm looking for a way to ignore the DST when the equipment have a #dst property (let's say) that tells wether its timestamps are DST aware.
ActiveSupport::TimeWithZone has a useful dst? method to check if the time is within Daylight Savings Time for the specified time zone:
Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
Time.zone.parse("2012-5-30").dst? # => true
Time.zone.parse("2012-11-30").dst? # => false
You can read more at http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-dst-3F
So you can just check if time is within DST, or extend TimeWithZone, as explained here Make Rails ignore daylight saving time when displaying a date by #zeantsoi:
class ActiveSupport::TimeWithZone
def no_dst
self.dst? ? self - 1.hour : self
end
end
and use it this way always getting the time without DST:
time.in_time_zone("Eastern Time (US & Canada)").no_dst

How can I validate and parse an ISO 8601 timestamp with user's time zone?

I need to be able to receive a user-input timestamp, with an optional time zone component, validate that is it a valid ISO 8601 time representation, and parse it according to the user's configured time zone.
I'm using Rails 4.2.6 on Ruby 2.3. I had hoped that Time.zone (ActiveSupport::TimeZone) would have an equivalent implementation to Time::iso8601 so that I could rescue ArgumentError exceptions to determine if the user input was a valid ISO 8601 time representation. Then I could do something like:
user_time_zone = 'America/Los_Angeles' # Would actually be from user's stored settings
params = {when: '2016-04-01T01:01:01'} # Would actually be from user input
# Would actually use Time::use_zone in around_action filter
Time.use_zone(user_time_zone) do
timestamp = Time.zone.iso8601 params[:when]
end
But, alas, no such method exists. And I can't find an equivalent one.
I can't use Time.zone.parse, because it treats ambiguous dates as valid (e.g. Time.zone.parse '04/11/16' # => Tue, 16 Nov 0004 00:00:00 LMT -07:52).
The best alternative I've been able to come up with so far is:
Time.use_zone(user_time_zone) do
old_tz = ENV['TZ']
ENV['TZ'] = Time.zone.name
timestamp = Time.iso8601 params[:when] # => 2016-04-01 01:01:01 -0700
ENV['TZ'] = old_tz
end
But this is ugly, messing around with an environment variable this way doesn't feel proper, and it and certainly isn't Rails-like. How can I validate and parse the time according to the user's time zone in a Rails way?
I suggest that you simply split the assignment into two steps: validate the ISO8601 format first and if valid, parse it:
user_time_zone = 'America/Los_Angeles'
params = { when: '2016-04-01T01:01:01' }
begin
Time.iso8601(params[:when]) # raises ArgumentError if format invalid
rescue ArgumentError => e
puts "invalid time format"
return
end
Time.use_zone(user_time_zone) do
timestamp = Time.zone.parse(params[:when])
end
I think you can still use an around_action for your use case. That's what I use and it works well for me.
In my ApplicationController I have:
class ApplicationController < ActionController::Base
around_action :set_time_zone
def set_time_zone
old_time_zone = Time.zone
Time.zone = user_time_zone
yield
ensure
Time.zone = old_time_zone
end
end
Any calls to Time will use the user's time zone within the scope of the request.

RoR: Parse datetime UTC string into timezone specific

Datetimes are being stored in MySQL as UTC.
In my app I've set: config.time_zone = 'UTC'
In my application_controller I set the users selected timezone:
around_filter :user_time_zone, :if => :current_user
def user_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
I can confirm this works by <%= Time.zone.now%> as it returns the user's set timezone
I then do a select query to grab the datetime field, and present it to the user as part of an array.
In my view, I have:
<%= Time.zone.parse(item[1]).strftime("%m/%d/%Y %I:%M %p") %>
which just outputs the datetime as-is (UTC), not in the user's specific time zone.
What do I need to change in order for the date and time to display correctly in the user's desired timezone?
Thanks for your time and assistance.
Edit:
Per Benjamin's suggestions, I had to modify it slightly to get it to work (was getting the same error) - Time.strptime(item[1], '%Y-%m-%d %H:%M:%S').in_time_zone(Time.zone) but 2 issues still remain.
1 - the original datetime is: 2013-07-25 22:27:50, but what is displayed is: 2013-07-25 16:27:50 -1000 my user timezone is Hawaii at -10 from UTC, but this only shows a 6 hr difference?
2 - How do I get this in a format that is easily readable by users (07/25/2013 12:27 PM)? Thanks again
Solved:
Thanks to Benjamin. I forgot to mention that I'm (stuck) using 1.8.7, so I had to work through a few minor differences between 1.8.7 and 1.9.3, but got it working with:
<%= DateTime.strptime(item[1], '%Y-%m-%d %H:%M:%S').in_time_zone(Time.zone).to_formatted_s(:long)%>
Updated: Got it into the format I wanted (06/20/2013 01:00 AM) using:
DateTime.strptime(item[1], '%Y-%m-%d %H:%M ').in_time_zone(Time.zone).strftime("%m/%d/%Y %I:%M %p")
Try this
Time.strptime(item[1], '%Y-%m-%dT%H:%M:%S%z').in_time_zone(Time.zone)
Answer to the bonus questions
Check the time zone of your db server and double check your rails data (default config / user TZ)
use to_formatted_s http://api.rubyonrails.org/classes/Time.html#method-i-to_formatted_s

Resources