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
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')
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.
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)
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/
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 ...