Parsing a custom DATE_FORMAT with DateTime in Rails - ruby-on-rails

How do I get DateTime to parse a custom date format(i.e. 'x-%Y')?
I've set the format within an initializer with (as per the RoR API):
Time::DATE_FORMATS[:x_year] = 'x-%Y'
Date::DATE_FORMATS[:x_year] = 'x-%Y'
and when I call:
DateTime.strptime('x-2011', 'x-%Y')
The correct result is returned, but
DateTime.parse('x-2011')
Throws an
ArgumentError: invalid date

never heard of such a possibility. However, you could still do something like:
class DateTime
class << self
alias orig_parse parse
end
def self.parse(string, format = nil)
return DateTime.orig_parse(string) unless format
DateTime.strptime(string, Date::DATE_FORMATS[format])
end
end
in your example it might look like that:
Date::DATE_FORMATS.merge!({:xform => "x-%Y"})
DateTime.parse('x-2011', :xform) #=> Sat, 01 Jan 2011 00:00:00 +0000
You could get rid of 'format' attribute and iterate && validate/rescue through DATE_FORMATS instead

Related

Format MomentJS to Rails DateTime

How do you format moment() to Rails datetime?
Im using react-datepicker and Rails 5 as the back end.
React:
let newDate = this.state.startDate.format('YYYY-MM-DD HH:mm')
console.log(newDate); #=> 2016-10-11 08:46
Looking at the momentjs formats, I see nothing close to rails datetime format ie: Mon, 10 Oct 2016 15:16:29 UTC +00:00
The aim is to pass newDate as params to a rails controller: params[:new_date]
Update:
cema-sp's answer was the final part to my answer but I was getting errors. To solved this:
# JS:
let newDate = this.state.startDate.format(); # no format type
# Controller:
DateTime.strptime(params[:new_date], '%Y-%m-%dT%H:%M:%S%z')
moment.js object newDate may be formatted in any string you like (for example like in the question YYYY-MM-DD HH:mm or default format) and sent to the server:
const newDate = this.state.startDate.format()
...
fetch(apiEndpointUrl, {
method: 'POST',
body: JSON.stringify({
new_date: newDate,
})
}).then(...)
...
The only thing is that you have to parse params[:new_date] on the Rails backend.
Use simple DateTime class parse method or more performant strptime (provide it choosen format).
If you want to update model you may use setter method:
def new_date=(value)
parsed = if value.is_a? String
DateTime.parse(value)
# Or more performant
# DateTime.strptime(value, '%Y-%m-%dT%H:%M:%S%z')
else
value
end
write_attribute(:new_date, parsed)
end
If you need to work with the date in controller, just parse it with one of DateTime methods:
new_date = DateTime.parse(params[:new_date])
# Or
new_date = DateTime.strptime(params[:new_date], '%Y-%m-%dT%H:%M:%S%z')

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.

Rails - Model Method for Default DateTime Format

In the DB, everything is in DateTime format. However, in my Post model, I would like to specify a default date and time format that is output whenever records are GET. Since this is for an API, I think I'll go with Twitter's format, unless anyone has any better suggestions:
"created_at":"Thu, 06 Oct 2011 19:41:12 +0000"
You could try overriding the as_json method on your model:
def as_json(options)
super(:methods => [:formatted_date])
end
Then have a method that formats the date they way you want
def formatted_date
# format date string here
end
This is assuming your controller is rendering json like format.json { render :json => #object }.
I would suggest using a builder to serialize data (jbuilder or active_model_serializers)

Rails ActiveRecord date parsing for i18n (specifically european date formats)

I'm working on a rails project for an Australian website. As a result, they want to be able to enter date formats in the more european-standard of 'dd/mm/yyyy' rather than the US-centric 'mm/dd/yyyy'. I have an ActiveRecord model with a Date field. I'm using jQuery's datepicker to provide the date select on a text field, and have it setting the date to a proper format. But, when I try to save the record, it gets the date wrong. Even when I've set the custom date formats in an intializer according to the i18n guide.
>> b = BlogPost.new
>> b.posted_on = '20/07/2010'
=> "20/07/2010"
>> b.posted_on
=> nil
>> b.posted_on = '07/20/2010'
=> Tue, 20 Jul 2010
It seems that Rails is just using Date.parse to convert the string into a Date object. Is there any way to fix this for the whole project? I don't want to have to write custom code for each model.
class Date
class << self
def _parse_with_us_format(date, *args)
if date =~ %r{^(\d+)/(\d+)/(\d+)$}
_parse_without_us_format("#{$3.length == 2 ? "20#{$3}" : $3}-#{$1}-#{$2}", *args)
else
_parse_without_us_format(date, *args)
end
end
alias_method_chain :_parse, :us_format
end
end
Try to change the default date format (in config/environment.rb)
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.
merge!(default => '%d/%m/%Y %H:%M')
Find out more here http://blog.nominet.org.uk/tech/2007/06/14/date-and-time-formating-issues-in-ruby-on-rails/

XML Serialization is not including milliseconds in datetime field from Rails model

By default, the datetime field from the database is being converted and stripping off the milliseconds:
some_datetime => "2009-11-11T02:19:36Z"
attribute_before_type_cast('some_datetime') => "2009-11-11 02:19:36.145"
If I try to overrride the accessor for this attribute like;
def some_datetime
attribute_before_type_cast('some_datetime')
end
when I try "to_xml" for that model, I get the following error:
NoMethodError (undefined method
`xmlschema' for "2009-11-11
02:19:36.145":String):
I have tried to parse the String to a Time object but can't get one to include the milliseconds;
def some_datetime
Time.parse(attribute_before_type_cast('some_datetime').sub(/\s/,"T").sub(/$/,"Z"))
end
Can anyone help get get a datetime with milliseconds rendered by to_xml?
As it turns out, I can exclude the original datetime field, and add a custom method which in turn renders the datetime as a string to the to_xml. This feels hackish, but it's working.. Is there another way to get milliseconds directly in the original datetime field?
In each model, I exclude "except" the field names that have datetimes that I want changed, and I include "methods" with the same name returning the attribute before it is typecasted.
def to_xml(options = {})
options[:methods] = [:some_datetime]
options[:except] = [:some_datetime]
super
end
def some_datetime
attribute_before_type_cast('some_datetime')
end
Rendering to_xml is working great with models included and any other options I pass in.
I have started to learn Ruby and was impressed by Mats "Principle of Least Surprise".
But the Date and Time implementation in Ruby ( and Rails ) is full of surprises:
Starting with a plain irb:
require 'time'
=> true
dt = Time.now
=> 2010-05-31 17:18:39 +0100
Time.parse(dt.to_s) == dt
=> false !?!?!?!?
dt.to_s(:db)
ArgumentError: wrong number of arguments(1 for 0)
from (irb):5:in to_s'
from (irb):5
from C:/Ruby19/bin/irb:12:in'
ok lets take some Rails:
sqlserver_test/development?: dt2 = Time.zone.now
=> Mon, 31 May 2010 17:24:54 CEST +02:00
sqlserver_test/development: dt2.class
=> ActiveSupport::TimeWithZone
sqlserver_test/development: Time.zone.parse(dt2.to_s) == dt2
=> false
sqlserver_test/development: dt2.to_s(:db)
=> "2010-05-31 15:24:54"
sqlserver_test/development: dt2.to_s(:iso8601)
=> "2010-05-31 17:24:54 +0200"
sqlserver_test/development: dt2.to_s(:iso8601) == dt2.iso8601
=> false
( all running on Ruby 1.9.1 with Rails 2.3.5 on Windows Xp )
Currently I only find several "hacks" regarding DateTime fields and databases
but no clean solution WITHOUT surprises ...

Resources