Converting time between timezone without changing the time? - ruby-on-rails

Our system currently defaults to Sydney time. I'm attempting to localize some reports to another timezone without changing the daily interval of the reports (i.e. 00:00:00 to 23:59:59).
Any time I attempt to convert the time object to another time zone it converts the hour/minutes/seconds along with the zone.
[16] pry(main)> t = Time.now
=> 2015-09-07 14:41:33 +1000
[17] pry(main)> t.in_time_zone("Darwin")
=> Mon, 07 Sep 2015 14:11:33 ACST +09:30
Can someone please point me in the right direction?
Thanks!

You can use Time::use_zone to override the time zone inside a given block:
t = Time.parse('2015-09-07 14:41:33 +1000')
#=> 2015-09-07 14:41:33 +1000
Time.use_zone('Darwin') { Time.zone.local(t.year, t.month, t.day, t.hour, t.min, t.sec) }
#=> Mon, 07 Sep 2015 14:41:33 ACST +09:30
Or the shortcut:
Time.use_zone('Darwin') { Time.zone.local(*t) }
#=> Mon, 07 Sep 2015 14:41:33 ACST +09:30
*t converts t to an array (using Time#to_a) and passes its elements as arguments:
The ten elements can be passed directly to ::utc or ::local to create a new Time object.

Maybe with regex is the most straightforward way:
t = Time.now
#=> 2015-09-07 05:53:23 +0000
Time.parse t.to_s.sub(/[+-]\d+$/, '+0930')
#=> 2015-09-07 05:53:23 +0930

I've found a solution.. Please feel free to leave an any answers that would be better as I am still a lowly junior.
I created the following method:
def export_in_timezone(time, zone)
Time.use_zone(zone) { time.to_datetime.change(offset: Time.zone.now.strftime("%z")) }
end
and pass in the time and timezone for each reporting being created.
Thanks!

Related

Rails - Convert time to string

This is the time format I want to convert
time = Time.parse('2020-07-02 03:59:59.999 UTC')
#=> 2020-07-02 03:59:59 UTC
I want to convert to string in this format.
"2020-07-02T03:59:59.999Z"
I have tried.
time.strftime("%Y-%m-%dT%H:%M:%S.999Z")
Is this correct? Any better way?
You can just use Time#iso8601 with the desired number of fraction digits as an argument:
time = Time.current.end_of_hour
time.iso8601(3) #=> "2020-07-01T10:59:59.999Z"
If you want to handle the output format explicitly via strftime, there are some things to keep in mind:
Instead of hard-coding 999, you should use %L to get the actual milliseconds:
time = Time.parse('2020-07-02 03:59:59.999 UTC')
#=> 2020-07-02 03:59:59 UTC
time.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
#=> "2020-07-02T03:59:59.999Z"
Use combinations for common formats, e.g. %F for %Y-%m-%d and %T for %H:%M:%S:
time.strftime('%FT%T.%LZ')
#=> "2020-07-02T03:59:59.999Z"
If you are dealing with time zones other than UTC (maybe your machine's local time zone), make sure to convert your time instance to utc first:
time = Time.parse('2020-07-02 05:59:59.999+02:00')
#=> 2020-07-02 05:59:59 +0200
time.utc
#=> 2020-07-02 03:59:59 UTC
time.strftime('%FT%T.%LZ')
#=> "2020-07-02T03:59:59.999Z"
or to use %z / %:z to append the actual time zone offset:
time = Time.parse('2020-07-02 05:59:59.999+02:00')
time.strftime('%FT%T.%L%:z')
#=> "2020-07-02T05:59:59.999+02:00"
For APIs you should use utc.iso8601:
> timestamp = Time.now.utc.iso8601
=> "2015-07-04T21:53:23Z"
See:
https://thoughtbot.com/blog/its-about-time-zones#working-with-apis

Elixir - Timex detecting when British Summer Time starts and ends

I found out that Timex.Timezone.convert(t, "Europe/London") returns a DateTime object of this format: #<DateTime(2019-04-24T17:00:00 Europe/London (+01:00:00))>. My question is when BST ends in October, will Timex.Timezone.convert(t, "Europe/London") automatically adjust and return the UTC time?
Yes, it will return +00:00 GMT Europe/London instead of +01:00 BST Europe/London.
As suggested in the comments, it's easy to check this: assuming that {:timex, "~> 3.0"} is added as a dependency, run
$iex -S mix
iex(1)> t = DateTime.from_naive!(~N[2019-11-01 13:26:08.003], "Etc/UTC")
#DateTime<2019-11-01 13:26:08.003Z>
iex(2)> Timex.Timezone.convert(t, "Europe/London")
#DateTime<2019-11-01 13:26:08.003+00:00 GMT Europe/London>
iex(3)> t = DateTime.from_naive!(~N[2019-10-01 13:26:08.003], "Etc/UTC")
#DateTime<2019-10-01 13:26:08.003Z>
iex(4)> Timex.Timezone.convert(t, "Europe/London")
#DateTime<2019-10-01 14:26:08.003+01:00 BST Europe/London>

Parse a date with too many days in month

Is there any way to correctly parse a date that has too many days in the month? E.g.
'2016-01-32' = '2016-02-01'
'2015-12-32' = '2016-01-01'
This is to support easy date manipulation on some of my JS based front-end.
This is with Ruby 2.3.0 and Rails 4.2.5. I think this was working in an earlier release - perhaps the parsing rules were tightened?
I'd rather rely on a standard library and it's knowledge of leap years etc, rather than implementing this myself.
You can use Date::_parse (or Date::_iso8601) to parse a date string without validation:
require 'date'
h = Date._parse('2016-01-32')
#=> {:mday=>32, :year=>2016, :mon=>1}
And create a date instance using Simone Carletti's suggestion:
Date.new(h[:year], h[:mon]).next_day(h[:mday] - 1)
#=> #<Date: 2016-02-01 ...>
If both, days and months can be out of range, you could use:
h = Date._parse('2016-14-32')
#=> {:mday=>32, :year=>2016, :mon=>14}
Date.new(h[:year]).next_month(h[:mon] - 1).next_day(h[:mday] - 1)
#=> #<Date: 2017-03-04 ...>
next_day and next_month are equivalent to + and << respectively, so the last line can also be written as:
Date.new(h[:year]) << (h[:mon] - 1) + (h[:mday] - 1)
or:
Date.new(h[:year]) << h[:mon].pred + h[:mday].pred
require 'date'
y, m, d = '2016-01-32'.split('-').map(&:to_i)
# => [2016, 01, 32]
last_day_of_month = Date.civil(y, m, -1)
# => #<Date: 2016-01-31 ((2457419j,0s,0n),+0s,2299161j)>
date = last_day_of_month + (d - last_day_of_month.day)
# => #<Date: 2016-02-01 ((2457420j,0s,0n),+0s,2299161j)>
Trying to parse a date like the one you mentioned will raise an error
DateTime.parse('2016-01-32')
ArgumentError: invalid date
One possible solution is to create a custom function/class for date parsing. You forward the parse to the DateTime object, if it fails with that error, you can retry by stripping the day number and replacing it with 1.
If it succeeds, you add the remaining days to the date.
date = DateTime.parse('2016-01-1')
=> Fri, 01 Jan 2016 00:00:00 +0000
date + (32-1).days
=> Mon, 01 Feb 2016 00:00:00 +0000
No existing core or Rails library will do that automatically for you. Hence you need a custom function for that.
You should be able to easily write the method following the instructions and the example above.
Here is one way to do it:
require 'date'
date_string = '2016-01-50'
year,month,day = date_string.split('-').map(&:to_i)
date = Date.new(year,month)
(day-1).times{date = date.next}
p date #=> 2016-02-19

.to_time different movement from documentation

I use rails4.2.4
My application.rb
config.time_zone = 'Tokyo'
Doc
http://api.rubyonrails.org/classes/String.html#method-i-to_time
say
"2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
"2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC
My code
I try this
p "2012-12-13T06:12".to_time
p "2012-12-13T06:12".to_time(:utc)
result
2012-12-13 06:12:00 +0900
2012-12-13 06:12:00 UTC
Summary
document say 2012-12-13 05:12:00 UTC
my code say 2012-12-13 06:12:00 UTC
doc 1 hour diffrence.
but my code same( I think occur 9 hours difference ).
Most likely it's just a typo in documentation. Because date/time in a string doesn't tell anything about time zone. So you can either assume it's a local time with .to_time(which is .to_time(:local)) or that this time is in UTC with .to_time(:utc) depending on application needs.

ArgumentError creating instance of Time class on Ruby version 1.8.6

I'm trying to get the time value using datetime_select from the view, return values obtained are:
"scheduletime"=>{"scheduletime(1i)"=>"2014", "scheduletime(2i)"=>"1", "scheduletime(3i)"=>"9", "scheduletime(4i)"=>"10", "scheduletime(5i)"=>"33"}
In controller I'm trying to create the instance of Time class using the return values of datetime_select. In following manner.
schedule_time = Time.new(params[:scheduletime]["scheduletime(1i)"], params[:scheduletime]["scheduletime(2i)"], params[:scheduletime]["scheduletime(3i)"], params[:scheduletime]["scheduletime(4i)"], params[:scheduletime]["scheduletime(5i)"],0, "+09:00")
Trying this I'm getting following error.
ArgumentError (wrong number of arguments (7 for 0))
The version of ruby I'm using is 1.8.6 . Can any one suggest me where I'm going wrong.
Time.new in Ruby 1.8.6 did not allowed any param. See the official documentation.
a = Time.new #=> Wed Apr 09 08:56:03 CDT 2003
b = Time.new #=> Wed Apr 09 08:56:03 CDT 2003
a == b #=> false
"%.6f" % a.to_f #=> "1049896563.230740"
"%.6f" % b.to_f #=> "1049896563.231466"
My suggestion is to use Time.utc in order to create a date with given params.
You can rewrite the code using Hash#valutes_at
Time.utc(*params[:scheduletime].values_at(%w( scheduletime(1i) scheduletime(2i) scheduletime(3i) scheduletime(4i) scheduletime(5i) )))
Note that Time.utc also accepts an optional time zone (given I see you are passing one).
As a side note, you definitely need to upgrade your Ruby version.
The documentation seems to point that you can't pass arguments to the Time.new method.
Try this instead (will use your current TimeZone):
schedule_time = Time.local(params[:scheduletime]["scheduletime(1i)"], params[:scheduletime]["scheduletime(2i)"], params[:scheduletime]["scheduletime(3i)"], params[:scheduletime]["scheduletime(4i)"], params[:scheduletime]["scheduletime(5i)"],0)
# some variants, using standard TimeZone:
Time.utc(2000,"jan",1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000
Time.gm(2000,"jan",1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000
Time.local(2000,"jan",1,20,15,1) #=> Sat Jan 01 20:15:01 CST 2000
You might want to set the TimeZone to a different one after that.

Resources