I just assumed that some rails magic would automatically convert an incoming post w/ a unix time 1345069440000 to the appropriate datetime on the backend. However, I have a model Event with a datetime called "start_at" and:
e = Event.new()
e.start_at = 1345069440000
e.save
It seems to send the 1345069440000 straight through and then mysql nulls it. Same with a ruby time
e = Event.new()
e.start_at = 1345069440
e.save
if I set it to some arbitrary strings, it does a better job of inferring:
e.start_at = '1/1344/12'
e.save
sets the date to '1334-12-01 00:00:00 UTC +00:00". So, it's making an attempt.
Clearly I can override the setter in my class, but I was hoping to change this behavior much higher up so that all controllers would support unix times for any datetime being passed up.
Rails 3.2, Ruby 1.9.2
Looks like this code from active_record/attribute_methods/time_zone_conversion.rb is attempting to do the conversion:
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
One option (albeit a little heavy-handed) would be to monkey-patch Fixnum to add a .to_time method:
def to_time
Time.at(self)
end
Related
I am trying to Create Date Format the US to Indian Date Format like(dd/mm/yyyy hh:mm tt).
When I run the code on my local machine it works.
When we publish and fetch values from the server at that time it shows "US" Date Format(mm/dd/yyyy)
How τo do the internal conversion, in Appsettings.json what strings i need to mention.
public static DateTime ConvertIndianDateFormat(DateTime usTime)
{
DateTime dateTime = DateTime.Now;
TimeZoneInfo usEasternZone = TimeZoneInfo.FindSystemTimeZoneById("US Eastern Standard Time");
TimeZoneInfo indianZone = TimeZoneInfo.FindSystemTimeZoneById("India Standard Time");
DateTime usEasternTime = TimeZoneInfo.ConvertTimeFromUtc(usTime, usEasternZone);
DateTime indianTime = TimeZoneInfo.ConvertTimeFromUtc(usTime, indianZone);
return indianTime;
}
This is because you are probably using something like DateTime.Now for C# and if you have an SQL Server you are using GETDATE(). It's not like an issue with application.json or something. The above functions return the machine datetime, thus why locally on your pc the time is correct and incorrect if you upload it to a server.
So make sure that the time is correct. If you are uploading to servers in another country then you will probably have a different time and/or format.
How you proceed depends on your needs:
Is the time correct?
Then simply reformated it or store it specifically using
DateTime.Now.ToString("dd/mm/yyyy hh:mm tt") // this is not the correct format.
Do you want to serve multiple clients in multiple regions/countries?
Then you should store the time as UTC and cast it based on clients date format. For example the server could be in USA and someone from UK would view a different time than his own which would be weird.
DateTime.UtcNow
Generally your problem could be large or small depending on your needs
I can mock Time.now with a great timecop gem.
Time.now
=> 2018-05-13 18:04:46 +0300
Timecop.travel(Time.parse('2018.03.12, 12:00'))
Time.now
=> 2018-03-12 12:00:04 +0300
TeacherVacation.first.ends_at
Thu, 15 Mar 2018 12:00:00 MSK +03:00
TeacherVacation.where('ends_at > ?', Time.now).count
1
But (obviously) this wouldn't work while using NOW() in a query:
TeacherVacation.where('ends_at > NOW()').count
0
Can I mock NOW() so that it would return the results for a certain time?
Timecop is a great gem! I would recommend using Timecop.freeze instead of traveling for your instance; you want to keep your tests deterministic.
As far as I could find, there doesn't seem to be a way to mock SQL's functions. Some languages like Postgres allow overloading functions, but you would still need a way to interject, and there doesn't seem to be a way to use environment variables in SQL.
A co-worker seemed to be certain you could actually drop system/language functions and make your own, but I was concerned about how to recover them after you do that. Trying to go that route sounds like a pain.
Solutions?
Here are a couple of "solutions" that I've come up with today while fighting this problem. Note: I don't really care for them to be honest, but if it gets tests in place ¯\_(ツ)_/¯ They at least offer a way to get things "working".
Unfortunately there's no snazzy gem to control the time in SQL. I imagine you would need something crazy like a plugin to the DB, a hack, a hook, a man in the middle, a container that you could trick SQL into thinking the system time was something else. None of those hack ideas would surely be portable/platform agnostic unfortunately either.
Apparently there are some ways to set time in a docker container, but that sounds like a painful overhead for local testing, and doesn't fit the granularity of a per-test time to be set.
Another thing to note, for me we're running large complex raw SQL queries, so that's why it's important that when I run the SQL file for a test I can have proper dates, otherwise I would just be doing it through activerecord like you mentioned.
String Interpolation
I ran across this in some large queries that were being ran.
This definitely helps if you need to push some environment variables through, and you can inject your own "current_date" if you want. This would help too if you needed to utilize a certain time across multiple queries.
my_query.rb
<<~HEREDOC
SELECT *
FROM #{#prefix}.my_table
WHERE date < #{#current_date} - INTERVAL '5 DAYS'
HEREDOC
sql_runner.rb
class SqlRunner
def initialize(file_path)
#file_path = file_path
#prefix = ENV['table_prefix']
#current_date = Date.today
end
def run
execute(eval(File.read #file_path))
end
private
def execute(sql)
ActiveRecord::Base.connection.execute(sql)
end
end
The Dirty Update
The idea is to update the value from ruby land pushing your "time-copped" time into the database to overwrite the value generated by the SQL DB. You may need to get creative with your update for times, like querying for a time greater than a given time that doesn't target your timecop time that you'll be updating rows to.
The reason I don't care for this method is because it ends up feeling like you're just testing activerecord's functionality since you're not relying on the DB to set values it should be setting. You may have computations in your SQL that you're then recreating in the test to set some value to the right date, and then you're no longer doing the computation in the SQL so then you're not even actually testing it.
large_insert.sql
INSERT INTO some_table (
name,
created_on
)
SELECT
name,
current_date
FROM projects
JOIN people ON projects.id = people.project_id
insert_spec.rb
describe 'insert_test.sql' do
ACTUAL_DATE = Date.today
LARGE_INSERT_SQL = File.read('sql/large_insert.sql')
before do
Timecop.freeze Date.new(2018, 10, 28)
end
after do
Timecop.return
end
context 'populated same_table' do
before do
execute(LARGE_INSERT_SQL)
mock_current_dates(ACTUAL_DATE)
end
it 'has the right date' do
expect(SomeTable.last.created_on).to eq(Date.parse('2018.10.28')
end
end
def execute(sql_command)
ActiveRecord::Base.connection.execute(sql_command)
end
def mock_current_dates(actual_date)
rows = SomeTable.where(created_on: actual_date)
# Use our timecop datetime
rows.update_all(created_on: Date.today)
end
Fun Caveat: specs wrap in their own transactions (you can turn that off, but it's a nice feature) so if your SQL has a transaction in it, you'll need to write code to remove it for the specs, or have your runner wrap your code in transactions if you need them. They'll run but then your SQL will kill off the spec transaction and you'll have a bad time. You can create a spec/support to help out with this if you go the route of cleaning up during tests, if I were in a newer project I would go with writing a runner that wraps the queries in transactions if you need them -- even though this isn't evident in the SQL files #abstraction.
Maybe there's something out there that lets you set your system time, but that sounds terrifying modifying your system's actual time.
I think the solution for this is DI (dependency injection)
def NOW(time = Time.now)
time
end
In test
current_test = Time.new(2018, 5, 13)
p NOW(current_test)
In production
p NOW
I have a database with measured values from devices that I want to display in a web frontend. First I send the list of devices to the frontend together with the IANA timezone specifier for each device.
I would like all timestamps to be exchanged as UTC. The user selects a time range in the frontend in device-local time. I use moment.js to convert these timestamps to UTC with the known timezone of the device like this:
var startTimestamp = new Date(2017, 7, 1); //some local timestamp (zero-based month!)
var m = moment.tz(startTimestamp, "Europe/Berlin");
var utc = moment.utc(m).format();
utc is now "2017-07-31T22:00:00Z" which seems to be correct given the 2 hours offset for Berlin in DST.
I send this utc timestamp to my ASP.NET Core backend. The controller looks like this:
[HttpGet]
public IEnumerable<TimestampedValue> GetValues(int id, DateTime startTimestamp)
{
...
}
The problem is that startTimestamp is 2017-08-01 00:00:00 when the controller is called and its Kind property is set to Local. I would have expected it to be the same UTC timestamp.
Any idea what I'm doing wrong? I think moment.js is doing its job correctly so this must be a problem on the server side. If I recall correctly, the deserialization is done by JSON.net but I don't understand why it does not respect the Z at the end of the time string.
After #dbc pointed me to the different behavior between GET and POST requests I come to this conclusion:
Since my request uses the GET method and query strings are not JSON, there is no JSON.net involved in the problem, it is the default .NET Core DateTimeConverter that does the conversion. Moment.js correctly converts the timestamp to a UTC string, I checked that using the browser developer tools.
The code for DateTimeConverter can be found here:
https://github.com/dotnet/corefx/blob/312736914d4e98c2948778bacac029aa831dd6b5/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/DateTimeConverter.cs
As can be seen there, the converter uses DateTime.Parse. It can be tested in a simple test project that DateTime.Parse does not respect the Z-suffix. This is also discussed here DateTimeConverter converting from UTC string.
I think there would be at least four solutions
1) write a custom model binder. These SOs each show a part of it Custom DateTime model binder in ASP.NET Core 1 (RTM)
https://dotnetcoretutorials.com/2016/12/28/custom-model-binders-asp-net-core/
2) write a custom type converter that overrides the default DateTime converter and checks whether there is a trailing Z. If so, use DateTime.Parse with the DateTimeStyles.AdjustToUniversal. Else fall back to the default implementation. I like this solution but I currently don't know how to replace the default DateTimeConverter.
3) replace all relevant DateTime parameters in the controllers with DateTimeOffset. DateTimeOffset seems to correctly convert the UTC string.
4) use a POST instead of a GET request with JSON in the request body. JSON.net seems to correctly convert the UTC string.
My preferred solution is currently a mixture of 3 and 4, depending on the context.
I have a custom module in openerp 7 with fields check-in time(date-time) and check-out time(date-time). When I click on save, i want to perform a validation on both fields to ensure check-out time is not less than check-in time. Thanks for any ideas.
As above, use datetime.
In Odoo your dates, times and datetimes are handed to use as strings formatted using
openerp.tools.DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_TIME_FORMAT and DEFAULT_SERVER_DATETIME_FORMAT.
from datetime import datetime
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
check_in = datetime.strptime(my_object.check_in, DEFAULT_SERVER_DATETIME_FORMAT)
check_out = datetime.strptime(my_object.check_out, DEFAULT_SERVER_DATETIME_FORMAT)
Go nuts with comparisons etc.
A couple of notes:
I highly recommend reading up on the datetime module in the standard library, particularly strftime, strptime and timedelta
Remember you will be getting the dates and datetimes in UTC. The classes that represent the date and datetime fields have methods to return dates and timestamps in the users' timezone but you will not usually need these. Have a look at fields.date.context_today and fields.datetime.context_timestamp
I would try to use the datetime class from the datetime module.
Import relevant python module
from datetime import datetime
Retrieve your record via the appropriate method i.e.
your_record = self.pool.get('your_custom_module').search(cr, uid, domain, offset=0, limit=None, order=None, context=None, count=False)
note: you need to provide proper domain and modify/remove arguments to suit you needs
Create datetime objects from relevant fields (use the strptime method of datetime class : create a date object from a string). Something like :
check_in = datetime.strptime(your_record[0]['check-in time'], '%Y-%m-%d')
check_out = datetime.strptime(your_record[0]['check-out time'], '%Y-%m-%d')
note: you need to adapt the format('%Y-%m-%d') to whatever format your DB returns
Compare both object with a simple expression :
if check_in < check_out:
...
else:
...
Do whatever other operations need to be done.
It's kinda hard to provide more info without additional details about your flow.
Hope this helps,
Cheers
The problem
I have a Ruby on Rails model with a Date attribute.
In the form for this model, I am using a single text field with a JQuery datepicker to represent this attribute (not a drop down for each of year, month, and day, as is the Rails custom).
The datepicker inserts dates with a mm/dd/yyyy format.
Rails is expecting dates with a dd/mm/yyyy format.
Examples
If a user selects March 12th, 2012, the datepicker puts 03/12/2012, which is interpreted by Rails as December 3rd, 2012.
If a user selects March 20th, 2012, the datepicker puts 03/20/2012, which is interpreted by Rails as the 3rd day of the 20th month of 2012. Since this date doesn't exist, Rails casts this to a nil value (I think).
Question
How do I change the date format Rails uses when parsing this date text field?
Notes:
1) I do not want to change the format of the date the datepicker inserts into the text field,
2) I am not asking about displaying my date attribute in a view.
I initially thought this could be solved through the Rails internationalization features, but it turns out I was wrong.
Ever since Ruby 1.9, the standard format for date parsing is dd/mm/yyyy, so as to better accomodate international users. More details can be found in this SO answer.
That standard is maintained in Rails, as Date.parse is now used to process data from form inputs. Using a before_validation callback won't work because the field is going to be received as nil by the callback method.
Right now there are two gems dealing with this specific issue, namely that date parsing in Rails does not follow the locale settings from I18n.locale. Both seem to work well.
delocalize, by clemens - Seems to have been applied successfully in a decent number or projects and has the highest number of stars at the moment.
i18n_alchemy by carlosantoniodasilva - This one has been released more recently. The author is a Rails core team member, and a very active one at that. Definitely deserves a look.
Since you don't want to change the picker's format, I would suggest you use a hidden field for the actual model property.
For example, add a hidden field for the model's date property, assuming you use a form builder as usual:
f.hidden_field :date
Then for the picker text input, don't bind it to the model's date property. Let's say the hidden field has ID 'modelname_date' and the picker text input has ID 'date_picker', use the following to make it work:
$(function(){
$("#date_picker").datepicker({altField: '#nodelname_date', altFormat: 'dd/mm/yyyy'});
});
In this way the date picker shows the date as 'mm/dd/yyyy' but Rails will see the date as 'dd/mm/yyyy'.
Update:
If you want to work this out on the Rails side, here's another solution I'd suggest:
Add a virtual property to your model: attr_accessor :bad_format_date
Add a before_validation callback in which you parse the input date and assign it to the real field:
before_validation do
self.date = Date.strptime(bad_format_date, "%m/%d/%Y")
end
Then for the form on the view use bad_format_date but initialize it with the date field value (if it's an edit form).
The timeliness gem makes ruby date/time parsing much more customizeable and integrates well with Rails.
Since you're working with Rails, be sure to check out the validates_timeliness project as well by the same guy. It includes all of timeliness plus sophisticated date/time validation methods for ActiveModel.
You could try do something like this.
$(function(){
$('#date_picker').datepicker( {
beforeShowDay: $.datepicker.noWeekends,
showOtherMonths: true,
selectOtherMonths: true,
dateFormat: 'dd-mm-yy',
defaultDate: date,
gotoCurrent: true
});
I just add the following monkey patch to config/time_formats.rb
class Date
class << self
alias :euro_parse :_parse
def _parse(str,comp=false)
str = str.to_s.strip
if str == ''
{}
elsif str =~ /^(\d{1,2})[-\/](\d{1,2})[-\/](\d{2,4})/
year,month,day = $3.to_i,$1,$2
date,*rest = str.split(' ')
year += (year < 35 ? 2000 : 1900) if year < 100
euro_parse("#{year}-#{month}-#{day} #{rest.join(' ')}",comp)
else
euro_parse(str,comp)
end
end
end
end