Why can not all DateTime instances be deserialized from YAML? - ruby

require 'time'
require 'yaml'
date = DateTime.parse('22018-01-05')
serialized_date = YAML.dump(date) # => "--- !ruby/object:DateTime 22018-01-05 00:00:00.000000000 Z\n"
YAML.load(serialized_date) # => NoMethodError (undefined method `captures' for nil:NilClass)
The same happens for Time and Date serializes it as a String.
For context the date string is actually a bad string, the year should be 2018.
ruby 2.6.0

It looks like this is because psych, in ScalarScanner#parse_time uses a regex which expects a 4 digit year.
EDIT: I have since opened a bug report with the psych project.

Related

Date.parse fails when system date is 2017-02-01

I'm experiencing an strange issue with Date.parse method.
I tried several ruby versions and it happens in all of them. The tests below were run in version 2.1.10.
Yesterday all my tests were passing but today they started to fail. The cause is a Date.parse call that started to raise an exception.
If system date is 2017-01-31, it works fine:
2.1.10 :002 > system('date')
Ter 31 Jan 2017 11:24:08 BRST
=> true
2.1.10 :003 > Date.parse("29%2F10%2F2015")
=> #<Date: 2017-01-29 ((2457783j,0s,0n),+0s,2299161j)>
But if system date is today, it fails:
2.1.10 :002 > system('date')
Qua 1 Fev 2017 11:24:27 BRST
=> true
2.1.10 :003 > Date.parse("29%2F10%2F2015")
ArgumentError: invalid date
from (irb):3:in `parse'
from (irb):3
from /Users/fernando/.rvm/rubies/ruby-2.1.10/bin/irb:11:in `<main>'
I probably can get around this by using another method to parse this date but I'm interested in why it started to fail today.
Is 2017-02-01 a special date for ruby?
Date.parse is a method which tries to parse a date from the given string using a number of heuristics in order to support many different formats without specifying the actual format. Thus, unless the format is clear, it is always possible that Ruby come to different conclusions than you.
In order to get an idea how Ruby parses your string, you can use
Date._parse("29%2F10%2F2015")
# => {:mday=>29}
As you can see, Ruby is able to get the day of month as 29 from the passed string but doesn't get any additional information. In order to form a valid date, Ruby substitutes the missing parts from the current date. Now, since February 2017 only has 28 days, the resulting date is invalid here but would be valid in January.
Still, the result is not what you actually seem to want. Instead, try to first transform your date into a more easily parsed string and try again using the approach by Eric Duminil in another answer to this question:
require 'date'
require 'uri'
string = '29%2F10%2F2015'
Date.strptime(URI.unescape(string), '%d/%m/%Y')
# => #<Date: 2015-10-29 ((2457325j,0s,0n),+0s,2299161j)>
As you can see, with Date.strptime, you can specify the exact format of the parsed string and can thus be sure it either gets correctly parsed or errors out.
%2F is the URL Encoded value of the Forward Slash (/)
so you need to decode your url-encoded string first
> require 'open-uri'
#=> true
> string = "29%2F10%2F2015"
#=> "29%2F10%2F2015"
> date = URI::decode(string)
#=> "29/10/2015"
> Date.parse(date)
#=> #<Date: 2015-10-29 ((2457325j,0s,0n),+0s,2299161j)>
Is 2017-02-01 a special date for ruby?
no, it's not special ;)
> s = "2017-02-01"
> Date.parse(s)
#=> #<Date: 2017-02-01 ((2457786j,0s,0n),+0s,2299161j)>
You have three problems :
'%2F' shouldn't be here
'2017-02-01' could be "February 1" or "January 2".
Date.parse relies on system date to parse the string.
If you know which format you have, you really should use Date.strptime :
require 'date'
require 'uri'
def parse_url_date(url_date)
Date.strptime(URI.unescape(url_date), '%d/%m/%Y')
end
puts parse_url_date("29%2F10%2F2015")
#=> 2015-10-29
puts parse_url_date("01%2F02%2F2017")
#=> 2017-02-01
If you know your "dates" are URL-encoded then you have to URL-decode them first. Use URI.unescape() for this then pass the value it returns to Date.parse().
Date.parse(URI.unescape("29%2F10%2F2015"))

invalid date using Ruby 1.9.2

Why is 'time' being returned as an invalid date?
val = "9/22/2011 4:23 AM"
time = DateTime.parse(val).strftime("%Y-%m-%d %H:%M:%S").to_datetime
#at breakpoint: time = 2011-09-22T04:23:00+00:00 as a DateTime Object
#form_entry.storage_datetime = time # crashes here with invalid date
If it helps, I'm using gem mysql 2.8.1 and Ruby 1.9.2. Thanks
I got an ArgumentError on line two; couldn't create the DateTime object in the first place.
Try using strptime instead:
val = "9/22/2011 4:23 AM"
DateTime.strptime(val, "%m/%d/%Y %H:%M %p")
=> #<DateTime: 2011-09-22T04:23:00+00:00 (3536390423/1440,0/1,2299161)>

Ruby DateTime.Parse to local time

I have a date string 20101129220021, so I will use
require 'date'
d = DateTime.parse('20101129220021')
This part works fine, and I get a date, which is in UTC.
My question is, how can I convert this into my local time? I tried many methods like extracting the time part using d.to_time and manipulate the result, but it didn't work. As far as I know, DateTime object is immutable. Can I please get some help?
irb(main):001:0> require "date"
=> true
irb(main):002:0> d = DateTime.parse('20101129220021')
=> #<DateTime: 2010-11-29T22:00:21+00:00 (70719276007/28800,0/1,2299161)>
irb(main):003:0> d.to_time
=> 2010-11-30 00:00:21 +0200
ruby 1.9.2p180 (2011-02-18)
You can add a rational fraction based on the timezone to get the local time.
require 'date'
# Make this whatever your zone is. Using UTC +0300 here.
ZONE = 3
d = DateTime.parse('20101129220021') + Rational(ZONE,24)
d.to_s # => "2010-11-30T01:00:21+00:00"

Graceful date-parsing in Ruby

I have two date parameters in a controller action that I would like to fall-back to a default value if they are nil, or parsing fails.
Unfortunately, it seems that DateTime.strptime throws an exception if parsing fails, which forces me to write this monstrosity:
starting = if params[:starting].present?
begin
DateTime.strptime(params[:starting], "%Y-%m-%d")
rescue
#meeting_range.first
end
else
#meeting_range.first
end
Feels bad man. Is there any way to parse a date with the Ruby stdlib that doesn't require a begin...rescue block? Chronic feels like overkill for this situation.
In general, I can't agree with the other solution, using rescue in this way is bad practice. I think it's worth mentioning in case someone else tries to apply the concept to a different implementation.
My concern is that some other exception you might be interested in will be hidden by that rescue, breaking the early error detection rule.
The following is for Date not DateTime but you'll get the idea:
Date.parse(home.build_time) # where build_time does not exist or home is nil
Date.parse(calculated_time) # with any exception in calculated_time
Having to face the same problem I ended up monkey patching Ruby as follows:
# date.rb
class Date
def self.safe_parse(value, default = nil)
Date.parse(value.to_s)
rescue ArgumentError
default
end
end
Any exception in value will be rose before entering the method, and only ArgumentError is caught (although I'm not aware of any other possible ones).
The only proper use of inline rescue is something similar to this:
f(x) rescue handle($!)
Update
These days I prefer to not monkey patch Ruby. Instead, I wrap my Date in a Rich module, which I put in lib/rich, I then call it with:
Rich::Date.safe_parse(date)
Why not simply:
starting = DateTime.strptime(params[:starting], '%Y-%m-%d') rescue #meeting_range.first
My preferred approach these days is to use Dry::Types for type coercions and Dry::Monads for representing errors.
require "dry/types"
require "dry/monads"
Dry::Types.load_extensions(:monads)
Types = Dry::Types(default: :strict)
Types::Date.try("2021-07-27T12:23:19-05:00")
# => Success(Tue, 27 Jul 2021)
Types::Date.try("foo")
# => Failure(ConstraintError: "foo" violates constraints (type?(Date, "foo"))
All of the existing answers do have rescue somewhere. However, we can use some "ugly" methods that was available from Ruby version 1.9.3 (it was there before but there is no official description).
The method is ugly because it starts with an underscore. However, it fits the purpose.
With this, the method call in the question can be written
starting = if params[:starting].present?
parsed = DateTime._strptime(params[:starting], "%Y-%m-%d") || {}
if parsed.count==3 && Date.valid_date?(parsed[:year], parsed[:month], parsed[:mday])
#meeting_range.first
end
else
#meeting_range.first
end
If the date string is matching the input format, _strptime will return a hash with all 3 date parts. so parsed.count==3 means all 3 parts exists.
However a further check that three parts forms a valid date in the calendar is still necessary since _strptime will not tell you they are not valid.
When you would to get date as object, parsed from string variable, sometimes passed string value may be nil, or empty, or invalid date string. I'd like to wrote safe metods for short:
def safe_date(string_date)
::Date.parse(string_date)
rescue TypeError, ::Date::Error
::Date.today
end
For example - check in irb console:
3.0.2 :001 > safe_date
=> #<Date: 2022-08-29 ((2459821j,0s,0n),+0s,2299161j)>
3.0.2 :001 > safe_date('')
=> #<Date: 2022-08-29 ((2459821j,0s,0n),+0s,2299161j)>
3.0.2 :002 > safe_date('29.12.2022')
=> #<Date: 2022-12-29 ((2459943j,0s,0n),+0s,2299161j)>
3.0.2 :003 > safe_date('29.13.2022')
=> #<Date: 2022-08-29 ((2459821j,0s,0n),+0s,2299161j)>

Is there a comprehensive library/module for ISO 8601 in ruby?

Is there already an implementation of all the date, time, duration and interval usage of the ISO 8601 standard in ruby? I mean something like a Class where you can set and get the details like, year, month, day, day_of_the_week, week, hour, minutes, is_duration?, has_recurrence? and so on which also can be set by and exported to a string?
require 'time'
time = Time.iso8601 Time.now.iso8601 # iso8601 <--> string
time.year # => Year of the date
time.month # => Month of the date (1 to 12)
time.day # => Day of the date (1 to 31 )
time.wday # => 0: Day of week: 0 is Sunday
time.yday # => 365: Day of year
time.hour # => 23: 24-hour clock
time.min # => 59
time.sec # => 59
time.usec # => 999999: microseconds
time.zone # => "UTC": timezone name
Have a look at the Time. It has a lot of stuff in it.
Unfortunately Ruby's built-in Date-Time functions do not seem to be well thought through (comparing to .NET for example), so for other functionality you will need to use some gems.
Good thing is that using those gems does feel like it's a built-into Ruby implementation.
Most useful probably is Time Calculations from ActiveSupport (Rails 3).
You don't need to require the rails but only this small library: gem install activesupport.
Then you can do:
require 'active_support/all'
Time.now.advance(:hours => 1) - Time.now # ~ 3600
1.hour.from_now - Time.now # ~ 3600 - same as above
Time.now.at_beginning_of_day # ~ 2010-11-24 00:00:00 +1100
# also at_beginning_of_xxx: xx in [day, month, quarter, year, week]
# same applies to at_end_of_xxx
There are really a lot of things that you can do and I believe you will find what suites your needs very well.
So instead of giving you abstract examples here I encourage you to experiment with irb requiring active_support from it.
Keep the Time Calculations at hand.
Ruby Time library adds an iso8601 method to the Time class. See here.
I don't know of a gem that exports the other ISO 8601 formats. You could extend the Time class yourself to add them.
Often you'll use the strftime method to print out specific formats. Example.

Resources