Any reason to use Date? - ruby

There are at least three types which represent time: Time, Date and DateTime(from ActiveSupport).
My problem is, could DateTime totally replace Date? In other words, if I can use DateTime, is there any reason to use Date instead?

require 'date'
d = Date.today
dt = DateTime.now
p Date.public_methods - DateTime.public_methods
#=>[:today]
p DateTime.public_methods - Date.public_methods
#=>[:now]
p d.public_methods - dt.public_methods
#=>[]
p dt.public_methods - d.public_methods
#=>[:hour, :min, :minute, :sec, :second, :sec_fraction, :second_fraction, :offset, :zone, :new_offset]
DateTime is a subclass of Date. Using DateTime, you lose the today Class method and get now in return. You don't lose instance methods.

If you want to store only the date, for example a birthday, or a certain day where an event takes place, then it can be easier to use only date. Then you have no troubles which arise from different time zones and time zone calculations. If you use DateTime, then if you add an offset of -2 hours to 00:00 am, you get 10:00 pm of the previous day.

Date does not store any information about the time, neither with the timezone. So you might get into trouble if at some point you'll need to use time data.
Cf this link, which I found clear about what classes should be used, when, and how.

Related

Find objects that match today's date in Ruby

I have a calendar object that lists a number of object events. Each event contains a start_date. So when I call
event.start_date it gives me "8/6/2017 3:00pm"
I want to be able to find all the events with today's date. I'm using Chronic. So
Chronic.parse('today') would give me "8/6/2017 00:00:00" or something like it
I'm not using Rails just Ruby. Thus, I don't have all the special methods that come with Rails to help me with this.
If I enter a specific date I could find the events with that specific date by using
event.start_date.starts_with?("8/6/2017")
but I haven't figured out how to do it with today's date.
Any help would be appreciated.
time = Chronic.parse('today')
target_date = Chronic.parse('8/6/2017').to_date
puts time.to_date == target_date
Time class (what Chronic returns) holds datetimes. Date only holds dates. If you convert Time to Date, you discard the time-of-day information, and then you can compare just the date itself.
Note that Date.today is the same thing as Chronic.parse('today').to_date (and same as Time.now.to_date). Also note that Time class doesn't have #to_date unless you require 'time', but Chronic does it for you.

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

How to understand strptime vs. strftime

What's the difference between strptime and strftime? I see that strptime is a method in the DateTime class, and strftime is a method in the Time class.
What's the difference between Time and DateTime, other than that they have different core methods? The explanation for the Time class in the Ruby docs is helpful, but the one for DateTime just says "datetime". There's also the Date class, which says it provides Date and DateTime. Help me make sense of this.
I see strptime and I want to pronounce it "strip time", but that doesn't make sense. Is there a good mnemonic-device for it?
What do strptime and strftime mean, anyway?
How do you remember which does what?
The difference between Time and DateTime has to do with implementation. A large amount of the DateTime functionality comes from the Rails world and is an arbitrary date with time of day. It's more of a calendar-based system. Time is measured as seconds since January 1, 1970 UTC and is time-zone agnostic. On some systems it is limited to values between 1901 and 2038, a limitation of how traditionally this value is stored as a signed 32-bit integer, but newer versions of Ruby can handle a much wider range, using a 64-bit value or BigNum as required.
In short, DateTime is what you get from a database in Rails where Time is what Ruby has traditionally used. If you're working with values where dates are important and you want to know things like the end of the month or what day it'll be six weeks ahead, use DateTime. If you're just measuring elapsed time and don't care about that, use Time. They're easy to convert between if necessary.
Date on the other hand is just a calendar date and doesn't have any associated times. You might want to use these where times are irrelevant.
strptime is short for "parse time" where strftime is for "formatting time". That is, strptime is the opposite of strftime though they use, conveniently, the same formatting specification. I've rarely seen strptime used since DateTime.parse is usually good at picking up on what's going on, but if you really need to spell it out, by all means use the legacy parser.
strptime means string parser, this will convert a string format to datetime.
Example:-
datetime.strptime('2019-08-09 01:01:01', "%Y-%m-%d %H:%M:%S")
datetime.datetime(2019, 8, 9, 1, 1, 1)//Result
strftime means string formatter, this will format a datetime object to string format.
Example:-
sample_date=datetime.strptime('2019-08-09 01:01:01', "%Y-%m-%d %H:%M:%S")
datetime.strftime(sample_date, "%Y-%d-%m %H:%M:%S")
'2019-09-08 01:01:01'//Result
I read the above answer and it is clear in its delineation of Time, DateTime and Date in Ruby.
Time is packaged with Ruby. It is measured as seconds since January 1, 1970 UTC and is time-zone agnostic. More specifically, the Time class stores integer numbers, which presents the seconds intervals since the Epoch. We can think of this as Unix Time. It has some limitations. I read somewhere if stored as a 64-bit signed integer, it can represent dates between 1823-11-12 to 2116-02-20, but on my system it can represent dates outside this range. If you do not specify the timezone to use in the enviroment variable ENV['TZ'], then it will default to your system time found in /etc/localtime on Unix-like systems. When to use Time? It is useful for measuring time elapse or interpolating a timestamp into a string value.
Rails actually extends the Time class. It accomplishes this through ActiveSupport::TimeWithZone. It provides support for configurable time zones. Note Rails will always convert time zone to UTC before it writes to or reads from the database, no matter what time zone you set in the configuration file. In other words, it is the default behaviour of Rails that all your time will get saved into database in UTC format.
# Get current time using the time zone of current local system or ENV['TZ'] if the latter is set.
Time.now
# Get current time using the time zone of UTC
Time.now.utc
# Get the unix timestamp of current time => 1524855779
Time.now.to_i
# Convert from unix timestamp back to time form
Time.at(1524855779)
# USE Rails implementation of Time! Notice we say Time.current rather than Time.now. This will allow you to use the timezone defined in Rails configuration and get access to all the timezone goodies provided by ActiveSupport::TimeWithZone.
Time.current
TimeWithZone provides a lot of very useful helper methods:
# Get the time of n day, week, month, year ago
1.day.ago
1.week.ago
3.months.ago
1.year.ago
# Get the beginning of or end of the day, week, month ...
Time.now.beginning_of_day
30.days.ago.end_of_day
1.week.ago.end_of_month
# Convert time to unix timestamp
1.week.ago.beginning_of_day.to_i
# Convert time instance to date instance
1.month.ago.to_date
For most cases, the Time with the time zone class from Rails’ ActiveSupport is sufficient. But sometimes you just need a date.
Just as with the Time class, Ruby is packaged with the Date class. Simply require the time library:
require "time"
Time.parse("Dec 8 2015 10:19")
#=> 2015-12-08 10:19:00 -0200
Date.parse("Dec 8 2015")
#=> #<Date: 2015-12-08>
Time.new(2015, 12, 8, 10, 19)
#=> 2015-12-08 10:19:00 -0200
Date.new(2015, 12, 8)
Since Date is part of Ruby, it by default uses the timezone defined in /etc/localtime on Unix-like systems, unless you modify the TZ environmental variable. Just as with the Time class, Rails extends the Date class. Use Date.current instead of Date.today to take advantage of ActiveSupport::TimeWithZone and use Rails-based timezone configurations.
Now there is one more class available with regards to dates and times. DateTime is a subclass of Date and can easily handles date, hour, minute, second and offset. It is both available in Ruby (via require 'time') and in Rails (via require 'date'). Rails extends it with TimeZone capabilities just like with the Time class.
require 'date'
DateTime.new(2001,2,3,4,5,6)
I personally do not see a need for using DateTime in your applications, for you can use Time itself to represent dates and times, and you can use Date to represent dates.
The second part of the question was regarding strptime and strftime. Time, Date and DateTime all have the strptime and strftime methods. strptime parses the given string representation and creates an object. Here is an example:
> result = Time.strptime "04/27/2018", "%m/%d/%Y"
=> 2018-04-27 00:00:00 -0400
> result.class
=> Time
This is useful if you have an application and a user submits a form and you are given a date and/or represented as a string. You will want to parse it into a Time or Date before you save it to the database.
strftime formats a date or time. So you call it on a Date or Time object:
> Date.current.strftime("%Y-%m-%d")
=> "2018-04-27"
And you can use them together to first parse user input and then format it in a certain way, perhaps to output into a csv file:
value = Date.strptime(val, '%m/%d/%Y').strftime('%Y-%m-%d')

How to get time_zone_options_for_select with DST offsets?

The ActionView::Helpers::FormOptionsHelper provides the time_zone_options_for_select to get a list of options for a select control that includes all of the timezones with their UTC offset. The problem I'm having is how to get it to display the correct offset for when daylight savings time is in effect?
For instance U.S. Mountain time is usually -7 UTC but during the summer it's effectively -6 UTC. Is there a way to have that list correctly reflect that?
TL;DR
The time zone data you are receiving is "correct" because ActiveSupport::TimeZone and TZInfo::Timezone instances do not make assumptions about the current date, and therefore applying DST to them doesn't make "sense" in the context of the responsibility of those objects.
You notice the problem because the default model for time_zone_options_for_select, ActiveSupport::TimeZone, unfortunately returns the offset string when calling #to_s, which, if the location is currently is observing DST, will be incorrect. There is no way to fix the string values generated in the options, or even remove the offset from them.
If this isn't acceptable you'll need to skip on using time_zone_options_for_select, and use options_for_select instead, and generate the options yourself.
Some investigation
time_zone_options_for_select uses ::ActiveSupport::TimeZone as the default model parameter, so passing it in manually will not change your results. In order to construct options for a select box, that method will construct an array of tuples in the format of [time_zone.to_s, time_zone.name], for the purpose of passing it to the more generic options_for_select method. time_zone, in this case, is an instance of ::ActiveSupport::TimeZone.
The important factor here is that this time zone instance object is, conceptually, completely unrelated/divorced from the idea of "the current date". The definition of a time zone (strictly speaking) has nothing to do with the current date. We can confirm this "not using DST" issue like so:
::ActiveSupport::TimeZone.all.find { |tz| tz.name == "Adelaide" }.utc_offset
=> 34200 # 9 hours and 30 minutes, in seconds
Adelaide's non-DST time zone is ACST (Australian Central Standard Time) which is GMT+9.5. Currently (as in the time of writing), Adelaide is in DST which means they are on ACDT (Australian Central Daylight Time), which is GMT+10.5.
::ActiveSupport::TimeZone.all.find { |tz| tz.name == "Adelaide" }.now.utc_offset
=> 37800 # 10 hours and 30 minutes, in seconds
The crucial difference here is essentially what I've outlined above - the ActiveSupport::TimeZone instance is just not concerned with the current date. The class itself is a convenience wrapper around a TZInfo::DataTimezone instance, which has similar opinions on the current date - none.
If you noticed, in the second code snippet above, we called #now on the time zone object before calling #utc_offset. This returns an ActiveSupport::TimeWithZone instance, which includes information about the time zone, as well as the current date - and therefore we get an offset which reflects the fact that the current date should include the DST offset.
So, the only problem here is that including the UTC offset string in the return value of #to_s on ActiveSupport::TimeZone instances is sort of misleading in this instance. It's included because it's the "base" UTC offset for that time zone.
Resources:
Rails 6.1 time_zone_options_for_select implementation
Related GitHub issue, rails/rails#7297
Related GitHub pull request, rails/rails#22243
I had similar problem but ended up using this
time_zone_select('time_zone', TZInfo::Timezone.us_zones,
:default => "America/Los_Angeles",
:model => TZInfo::Timezone
Did you find a better solution?

Yearless Ruby dates?

Is there a way to represent dates like 12/25 without year information? I'm thinking of just using an array of [month, year] unless there is a better way.
You could use the Date class and hard set the year to a leap year (so that you could represent 2/29 if you wanted). This would be convenient if you needed to perform 'distance' calculations between two dates (assuming that you didn't need to wrap across year boundaries and that you didn't care about the off-by-one day answers you'd get when crossing 2/29 incorrectly for some years).
It might also be convenient because you could use #strftime to display the date as (for example) "Mar-3" if you wanted.
Depending on the usage, though, I think I would probably represent them explicitly, either in a paired array or something like YearlessDate = Struct.new(:month,:day). That way you're not tempted to make mistakes like those mentioned above.
However, I've never had a date that wasn't actually associated with a year. Assuming this is the case for you, then #SeanHill's answer is best: keep the year info but don't display it to the user when it's not appropriate.
You would use the strftime function from the Time class.
time = Time.now
time.strftime("%m/%d")
While #Phrogz answer makes perfect sense, it has a downside:
YearlessDate = Struct.new(:month,:day)
yearless_date = YearlessDate.new(5, 8)
This interface is prone to MM, DD versus DD, MM confusion.
You might want to use Date instead and consider the year 0 as "yearless date" (provided you're not a historian dealing with real dates around bc/ad of course).
The year 0 is a leap year and therefore accommodates every possible day/month duple:
Date.parse("0000-02-29").leap? #=> true
If you want to make this convention air tight, just define your own class around it, here's a minimalistic example:
class YearlessDate < Date
private :year
end
The most "correct" way to represent a date without a year is as a Fixnum between 001 and 365. You can do comparisons on them without having to turn it into a date, and can easily create a date for a given year as needed using Date.ordinal

Resources