Time.strptime doesn't parse week formatters? - ruby

Using Ruby 2.1, I am trying to find the reciprocal of Time#strftime('%Y%U'). For example:
s = Time.parse("2014-05-07 16:41:48 -0700").strftime('%Y%U')
# 201418
t = Time.strptime(s, '%Y%U')
# Expected: 2014-05-04 00:00:00 -0700
# Actual: 2014-01-01 00:00:00 -0800
This topic suggested to use %G so I read the docs and tried it, but all I get out of it is the current Time. eg:
t = Time.strptime('201418', '%G%U')
# 2014-05-13 12:07:51 -0700
From the docs, it looks to me that %G is only intended to work with %V as both are ISO 8601 and %U is not, but even even using %G%V I get back the current time.
So what's the right way to turn a %Y%U string into the corresponding Time?

This could be a bug, since it seems to work fine if you use DateTime instead.
s = Time.parse("2014-05-07 16:41:48 -0700").strftime('%Y%U')
DateTime.strptime(s, "%Y%U")
#<DateTime: 2014-05-04T00:00:00+00:00 ((2456782j,0s,0n),+0s,2299161j)>
Edit:
If you look at the source code this is actually just dumb coding. Time.strptime calls Date._strptime which correctly parses out the year and week number. But then Time.strptime stupidly only looks for the usual year, month, day, hour, etc. and ignores the week number entirely.
I submitted a bug report.

Related

Plain Ruby: set only timezone for a DateTime instance

All the existing answers either use a lib or Rails. I need to do this in plain Ruby. I also find it hard to imagine that a beautiful language like Ruby would make this so difficult.
I have many date strings of the form: 07 Nov 20. I am able to parse these into a DateTime instance using:
> require 'time'
=> true
> DateTime.strptime("07 Nov 20", "%d %b %y")
=> #<DateTime: 2020-11-07T00:00:00+00:00 ((2459161j,0s,0n),+0s,2299161j)>
> _.new_offset("+05:30")
=> #<DateTime: 2020-11-07T05:30:00+05:30 ((2459161j,0s,0n),+19800s,2299161j)>
As we can see, using DateTime#new_offset changes not only the offset, but also change the time. There doesn't seem to exist any other method to only change the timezone and/or offset either.
If possible, I'd also like to do this for that individual DateTime instance and not all DateTime instances created in the future via some TIMEZONE global or something.
Note: While the input string doesn't contain any time component, I still need my runtime representation to have one. So while using a simpler method like Date#strptime would work, it is not "ideal".
Note 2: I'm guessing there are other complications with simply "setting" the timezone, which is why this function is not present. For example it is not obvious how to handle DST when you "set" the timezone. I'm guessing that changing the time is going to be inevitable if you want to ensure that passing in a valid offset will always return a valid DateTime instance.
Edit 1:
Further digging in the source code for date (date/date_core.c) reveals the set_of function is used to set the offset for a DateTime instance. Unfortunately, it seems that the logic of manipulating the offset is coupled with the manipulation of the time. This explains why there is no method exposed in the Ruby stdlib to only set the offset.
Should they be Date objects instead? Dates don't have times nor time zones.
Date.strptime("07 Nov 20", "%d %b %y")
Otherwise, you can add a time zone to the string and parse it.
string = "07 Nov 20"
p DateTime.strptime("#{string}+0530", "%d %b %y %z")
Or you can subtract the extra hours. DateTime subtracts number of days and will take a fraction.
DateTime.strptime("07 Nov 20", "%d %b %y").new_offset("+05:30") - 5.5/24

How to get date time from DB date in Ruby

I need to get time and date from database date like "2015-08-27T12:09:36Z". I tried but not get any solutions where I get date and time in different variable.
I need to get it in Ruby. No rails in my application.
I used below code but not getting.
Time.strptime("2015-08-27T12:09:36Z", "%Y-%m-%dT%H:%M:%S%z").in_time_zone
Any one have a experience in it?
Thanks
I don't have enough reputations to comment so am posting comment as answer, are you looking for this
Time.now.strftime('%Y-%m-%dT%H:%M:%SZ')
Which will give the pattern you asked for. Z represent the time zone if you use
%z - Time zone as hour and minute offset from UTC (e.g. +0900)
%:z - hour and minute offset from UTC with a colon (e.g. +09:00)
%::z - hour, minute and second offset from UTC (e.g. +09:00:00)
%Z - Time zone abbreviation name
Check for more
http://apidock.com/ruby/Time/strftime
My Updated answer after your comment
require 'date'
DateTime.parse("2015-08-27T12:09:36Z").strftime('%Y-%m-%dT%H:%M:%SZ')
In your code change Time.strptime('') to DateTime.strptime('')
First, you need to require 'date'. Ruby has built-in Date and Time classes without that require, but the library provides more functionality.
If you have a string retrieved from the database in ISO-8601 format, and you want to turn it into a date and time, just use DateTime.iso8601(string) to get a DateTime object. You can extract the date and time components however you like after that.
irb(main):001:0> require 'date' #=> true
irb(main):002:0> dt = DateTime.iso8601("2015-08-27T12:09:36Z") # DateTime object
irb(main):003:0> d = dt.to_date # Date object
irb(main):004:0> t = dt.to_time # Time object

Is Date.today in UTC?

Calling Date.today in Ruby returns the current date. However, what timezone is it in? I assume UTC, but I want to make sure. The documentation doesn't state either way.
You can get away by using
Time.now.utc.to_date
in ruby
TL;DR: Date.today uses the system’s local time zone. If you require it be in UTC, instead get the date from a UTC time, e.g. Time.now.utc.to_date.
Dates do not have timezones, since they don't represent a time.
That said, as for how it calculates the current day, let's look at this extract from the code for Date.today:
time_t t;
struct tm tm;
// ...
if (time(&t) == -1)
rb_sys_fail("time");
if (!localtime_r(&t, &tm))
rb_sys_fail("localtime");
It then proceeds to use use tm to create the Date object. Since tm contains the system's local time using localtime(), Date.today therefore uses the system's local time, not UTC.
You can always use Time#utc on any Time convert it in-place to UTC, or Time#getutc to return a new equivalent Time object in UTC. You could then call Time#to_date on that to get a Date. So: some_time.getutc.to_date.
If you’re using ActiveSupport’s time zone support (included with Rails), note that it is completely separate from Ruby’s time constructors and does not affect them (i.e. it does not change how Time.now or Date.today work). See also ActiveSupport extensions to Time.
An instance of Date is represented as an Astronomical Julian Day Number; it has no fractional part of a day. While a Julian Day is relative to GMT - so technically an instance of Date should be considered to be in GMT - for most purposes you can ignore that and treat it as having no timezone. You would only care about a time zone if you converted it to a DateTime or Time.
ActiveSupport's tools for date conversion let you specify whether you want local time or UTC.
E.g.:
>> t = Date.today.to_time
=> Wed Apr 18 00:00:00 -0400 2012
>> t.zone
=> "EDT"
>> t = Date.today.to_time(:utc)
=> Wed Apr 18 00:00:00 UTC 2012
>> t.zone
=> "UTC"
You can use
Date.current
To get the current date on the configured timezone.
If your rails applications is configured to run in time zone UTC then just use Time.zone.today, otherwise force it by using Time.now.utc.to_date
Ruby
# Using local timezone
Date.today
Ruby on Rails
# Using UTC
Date.yesterday
Date.current
Date.tomorrow
This is specifically nasty because when using e.g. Date.today and Date.yesterday in conjunction, it might produce quite unexpected results depending on the timezone and the current time.
For example, at 00:30 in timezone CET (UTC+1) Date.yesterday will be two days before Date.today. In other cases, Date.today and Date.yesterday can have the same value.

Is there a full implementation for ISO-8601 date parsing for Ruby?

The Time.iso8601 method is a restricted subset of ISO-8601.
What are its limitations?
Does anyone know of a full implementation for Ruby? I'm using MRI 1.8.7.
Update
It looks like there isn't a single class that handles all of the various 8601 date and date/time combinations. However, I have managed to work around the problems by using both the Date.parse and Time.iso8601 methods. The downside is that you need to decide in code whether the input looks like a date or a date/time.
Warning : Timezone differences
Time.iso8601 and Time.parse behave differently.
>> Time.parse("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 18:27:00 +0100 2010
>> Time.iso8601("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 17:27:00 UTC 2010
Differences between Time.iso8601 and ISO-8601
This document touches on the differences between what is in ISO-8601 and what is supported by Ruby. The short answer is that the number of possible formats is restricted.
Yes, but unfortunately it's in Ruby 1.9.
require "date"
Date.iso8601("2010-W32-5").strftime
#=> "2010-08-13"
I don't believe there are any implementations for Ruby 1.8.7 (or at least I couldn't find any). You could either try to upgrade to Ruby 1.9, which is pretty stable as of 1.9.2. Alternatively, you could try to parse the dates yourself.
To convert an ISO8601 date into the local time zone, do this:
require "time"
dt1 = Time.parse("2010-09-06T12:27:00.10-05:00")
To convert an ISO8601 date into UTC, do this:
dt2 = Time.iso8601("2010-09-06T12:27:00.10-05:00")
If you compare the dates returned by the above queries, they will be identical (i.e. dt1 === dt2). However, accessing date components (like year, month, day, hour, etc.) will return values appropriate for the time zone (either UTC or local). The same applies to strftime.

Difference between DateTime and Time in Ruby

What's the difference between DateTime and Time classes in Ruby and what factors would cause me to choose one or the other?
Newer versions of Ruby (2.0+) do not really have significant differences between the two classes. Some libraries will use one or the other for historical reasons, but new code does not necessarily need to be concerned. Picking one for consistency is probably best, so try and mesh with what your libraries expect. For example, ActiveRecord prefers DateTime.
In versions prior to Ruby 1.9 and on many systems Time is represented as a 32-bit signed value describing the number of seconds since January 1, 1970 UTC, a thin wrapper around a POSIX-standard time_t value, and is bounded:
Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901
Newer versions of Ruby are able to handle larger values without producing errors.
DateTime is a calendar-based approach where the year, month, day, hour, minute and second are stored individually. This is a Ruby on Rails construct that serves as a wrapper around SQL-standard DATETIME fields. These contain arbitrary dates and can represent nearly any point in time as the range of expression is typically very large.
DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000
So it's reassuring that DateTime can handle blog posts from Aristotle.
When choosing one, the differences are somewhat subjective now. Historically DateTime has provided better options for manipulating it in a calendar fashion, but many of these methods have been ported over to Time as well, at least within the Rails environment.
[Edit July 2018]
All of the below still holds true in Ruby 2.5.1. From the reference documentation:
DateTime does not consider any leap seconds, does not track any summer time rules.
What hasn't been noted in this thread before is one of the few advantages of DateTime: it is aware of calendar reforms whereas Time is not:
[…] Ruby's Time class implements a proleptic Gregorian calendar and has no concept of calendar reform […].
The reference documentation concludes with the recommendation to use Time when exclusively dealing with near-past, current or future dates/times and only use DateTime when, for example, Shakespeare's birthday needs to be accurately converted: (emphasis added)
So when should you use DateTime in Ruby and when should you use Time? Almost certainly you'll want to use Time since your app is probably dealing with current dates and times. However, if you need to deal with dates and times in a historical context you'll want to use DateTime […]. If you also have to deal with timezones then best of luck - just bear in mind that you'll probably be dealing with local solar times, since it wasn't until the 19th century that the introduction of the railways necessitated the need for Standard Time and eventually timezones.
[/Edit July 2018]
As of ruby 2.0, most of the information in the other answers is out of date.
In particular, Time is now practically unbound. It can be more or less than even 63 bits away from Epoch:
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> Time.at(2**62-1).utc # within Integer range
=> 146138514283-06-19 07:44:38 UTC
irb(main):003:0> Time.at(2**128).utc # outside of Integer range
=> 10783118943836478994022445751222-08-06 08:03:51 UTC
irb(main):004:0> Time.at(-2**128).utc # outside of Integer range
=> -10783118943836478994022445747283-05-28 15:55:44 UTC
The only consequence of using larger values should be performance, which is better when Integers are used (vs. Bignums (values outside of Integer range) or Rationals (when nanoseconds are tracked)):
Since Ruby 1.9.2, Time implementation uses a signed 63 bit integer, Bignum or Rational. The integer is a number of nanoseconds since the Epoch which can represent 1823-11-12 to 2116-02-20. When Bignum or Rational is used (before 1823, after 2116, under nanosecond), Time works slower as when integer is used.
(http://www.ruby-doc.org/core-2.1.0/Time.html)
In other words, as far as I understand, DateTime no longer covers a wider range of potential values than Time.
In addition, two previously unmentioned restrictions of DateTime should probably be noted:
DateTime does not consider any leapseconds, does not track any summer time rules.
(http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime)
First, DateTime has no concept of leap seconds:
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.new(2012,6,30,23,59,60,0)
=> 2012-06-30 23:59:60 +0000
irb(main):004:0> dt = t.to_datetime; dt.to_s
=> "2012-06-30T23:59:59+00:00"
irb(main):005:0> t == dt.to_time
=> false
irb(main):006:0> t.to_i
=> 1341100824
irb(main):007:0> dt.to_time.to_i
=> 1341100823
For the above example to work with Time, the OS needs to support leap seconds and timezone information needs to be set correctly, e.g. through TZ=right/UTC irb (on many Unix systems).
Second, DateTime has very limited understanding of time zones and in particular has no concept of daylight savings. It pretty much handles time zones as simple UTC + X offsets:
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.local(2012,7,1)
=> 2012-07-01 00:00:00 +0200
irb(main):004:0> t.zone
=> "CEST"
irb(main):005:0> t.dst?
=> true
irb(main):006:0> dt = t.to_datetime; dt.to_s
=> "2012-07-01T00:00:00+02:00"
irb(main):007:0> dt.zone
=> "+02:00"
irb(main):008:0> dt.dst?
NoMethodError: undefined method `dst?' for #<DateTime:0x007f34ea6c3cb8>
This may cause trouble when times are entered as DST and then converted into a non-DST time zone without keeping track of the correct offsets outside of DateTime itself (many operating systems may actually already take care of this for you).
Overall, I'd say that nowadays Time is the better choice for most applications.
Also note an important difference on addition: when you add a number to a Time object, it is counted in seconds, but when you add a number to a DateTime, it is counted in days.
I think the answer to "what's the difference" is one of the unfortunate common answers to this question in the Ruby standard libraries: the two classes/libs were created differently by different people at different times. It's one of the unfortunate consequences of the community nature of Ruby's evolution compared to carefully planned development of something like Java. Developers want new functionality but don't want to step on existing APIs so they just create a new class - to the end user there's no obvious reason for the two to exist.
This is true for software libraries in general: often the reason some code or API is the way it is turns out to be historical rather than logical.
The temptation is to start with DateTime because it seems more generic. Date... and Time, right? Wrong. Time also does dates better, and in fact can parse timezones where DateTime can't. Also it performs better.
I've ended up using Time everywhere.
To be safe though, I tend to allow for DateTime arguments to be passed into my Timey APIs, and either convert. Also if I know that both have the method I'm interested in I accept either, like this method I wrote for converting times to XML (for XMLTV files)
# Will take a date time as a string or as a Time or DateTime object and
# format it appropriately for xmtlv.
# For example, the 22nd of August, 2006 at 20 past midnight in the British Summertime
# timezone (i.e. GMT plus one hour for DST) gives: "20060822002000 +0100"
def self.format_date_time(date_time)
if (date_time.respond_to?(:rfc822)) then
return format_time(date_time)
else
time = Time.parse(date_time.to_s)
return format_time(time)
end
end
# Note must use a Time, not a String, nor a DateTime, nor Date.
# see format_date_time for the more general version
def self.format_time(time)
# The timezone feature of DateTime doesn't work with parsed times for some reason
# and the timezone of Time is verbose like "GMT Daylight Saving Time", so the only
# way I've discovered of getting the timezone in the form "+0100" is to use
# Time.rfc822 and look at the last five chars
return "#{time.strftime( '%Y%m%d%H%M%S' )} #{time.rfc822[-5..-1]}"
end
I found such things like parsing and calculating the beginning/end of a day in different timezones are easier to do with DateTime, assuming you are using the ActiveSupport extensions.
In my case I needed to calculate the end of the day in a user's timezone (arbitrary) based on the user's local time which I received as a string, e.g. "2012-10-10 10:10 +0300"
With DateTime it's as simple as
irb(main):034:0> DateTime.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 +0300
# it preserved the timezone +0300
Now let's try it the same way with Time:
irb(main):035:0> Time.parse('2012-10-10 10:10 +0300').end_of_day
=> 2012-10-10 23:59:59 +0000
# the timezone got changed to the server's default UTC (+0000),
# which is not what we want to see here.
Actually, Time needs to know the timezone before parsing (also note it's Time.zone.parse, not Time.parse):
irb(main):044:0> Time.zone = 'EET'
=> "EET"
irb(main):045:0> Time.zone.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 EEST +03:00
So, in this case it's definitely easier to go with DateTime.
Consider how they handle timezones differently with custom instantiations:
irb(main):001:0> Time.new(2016,9,1)
=> 2016-09-01 00:00:00 -0400
irb(main):002:0> DateTime.new(2016,9,1)
=> Thu, 01 Sep 2016 00:00:00 +0000
irb(main):003:0> Time.new(2016,9,1).to_i
=> 1472702400
irb(main):004:0> DateTime.new(2016,9,1).to_i
=> 1472688000
This can be tricky when creating time ranges, etc.
In addition to the answer of Niels Ganser you might consider this argument:
Note that The Ruby Style Guide quite clearly states a position on this:
No DateTime
Don’t use DateTime unless you need to account for historical calendar
reform - and if you do, explicitly specify the start argument to
clearly state your intentions.
# bad - uses DateTime for current time
DateTime.now
# good - uses Time for current time
Time.now
# bad - uses DateTime for modern date
DateTime.iso8601('2016-06-29')
# good - uses Date for modern date
Date.iso8601('2016-06-29')
# good - uses DateTime with start argument for historical date
DateTime.iso8601('1751-04-23', Date::ENGLAND)
It seems that in some cases the behavior is very different:
Time.parse("Ends from 28 Jun 2018 12:00 BST").utc.to_s
"2018-06-28 09:00:00 UTC"
Date.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s
"2018-06-27 21:00:00 UTC"
DateTime.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s
"2018-06-28 11:00:00 UTC"

Resources