Timezone Offset in Angular JS and Rails - ruby-on-rails

Background: I'm building an app with Angular JS as web interface and Rails API. The problem I am having is passing a date from Angular to Rails.
Issue: I have a form with a Date of Birth date field, when a user inputs his DOB say March 1st, 1985, Angular interprets it as 1985-03-01 00:00 +0800 (if you're in Hong Kong or Singapore) and sends a request to Rails. The first thing Rails does with it is to convert it to UTC, which means the datetime is now 1985-02-28 16:00 UTC. Therefore, when the date is saved to the database date column, it becomes Feb 28, 1985.
Solution for now: What I'm doing now is on Angular side, I get the Timezone offset hours and add it to the date, so instead of 1985-03-01 00:00 +0800, it is now 1985-03-01 08:00 +0800. When Rails get it, it converts to 1985-03-01 00:00 UTC and so saves the correct date to db. However, I believe this is a better alternative to tackle this issue.
Thinking about parsing just the date in Rails, yet the params[:dob] I see is already UTC by the time I get it. Would love to know if there is a better practice than my current solution. Thank you for any comment and feedback.

This problem is actually quite common, and stems from two separate but related issues:
The JavaScript Date object is misnamed. It's really a date + time object.
The JavaScript Date object always takes on the characteristics of the time zone for the environment in which it is running in.
For a date-only value like date-of-birth, the best solution to this problem is to not send a full timestamp to your server. Send just the date portion instead.
First, add 12 hours to the time, to use noon instead of midnight. This is to avoid issues with daylight saving time in time zones like Brazil, where the transition occurs right at midnight. (Otherwise, you may run into edge cases where the DOB comes out a day early.)
Then output the date portion of the value, as a string in ISO format (YYYY-MM-DD).
Example:
var dt = // whatever Date object you get from the control
dt.setHours(dt.getHours() + 12); // adjust to noon
var pad = function(n) { return (n < 10 ? '0' : '') + n; }
var dob = dt.getFullYear() + '-' + pad(dt.getMonth()+1) + '-' + pad(dt.getDate());
Another common way to do this is:
var dt = // whatever Date object you get from the control
dt.setHours(dt.getHours() + 12); // adjust to noon
dt.setMinutes(dt.getMinutes() - dt.getTimezoneOffset()); // adjust for the time zone
var dob = dt.toISOString().substring(0,10); // just get the date portion
On the Rails side of things, use a Date object instead of a DateTime. Unlike JavaScript, the Rails Date object is a date-only object - which is perfect for a date-of-birth.

Related

How can I add timezone to Esper queries?

I am using Esper & I need to filter events by their timestamp. The events come from an external source.
The challenge is that the cutoff instant is at a different timezone than the events` timestamp, e.g. the cutoff instant is at 3:30 CET (e.g. Prague time) while the timestamp field of the event is at UTC.
This poses a problem when the timezone shifts to Daylight Savings Time, because the cutoff instant needs to be modified in the query. E.g. in this case, if the cutoff instant is 3:30 CET, during winter time it would be on 2:30 UTC and during DST it would be on 1:30 UTC. It means that I have to change the query when the time shifts into and out of DST.
This is the current query:
SELECT *
FROM my_table
WHERE timestamp_field.after( timestamp.withtime(2,30,0,0) )
I would like to have a robust solution that will save me the hassle of changing the cutoff timestamp queries every few months. Can I add the timezone to the query statement itself? Is there any other solution?
It may help to add an event property to the event that represents UTC time i.e. normalize the event timestamp to UTC and use the normalized property instead.
The query could also use a variable instead of the hardcoded numbers. Another option would perhaps be changing Esper source to take in a timezone for some func.s
After struggling unsuccessfully with trying ot do it in the WHERE caluse or using a Pattern, I managed to solve the issue using a [Single-Row Function plugin][1].
I pass the plugin function the cutoff hour, timezone & event timezone and compute the cutoff hour in the event's timezone.
My query changed to:
SELECT *
FROM my_table
WHERE timestamp_field.after( timestamp.withtime(
eventTZHour(2, 'UTC', 'Europe/Prague'), 30, 0, 0) )
I added the Java implementation in a class:
public class EsperPlugins {
public int eventTZHour(int hour, String eventTZ, String cutoffTZ) {
// return tz calculations
}
}
and finally registered the plugin in esper.cfg.xml:
<esper-configuration>
<plugin-singlerow-function name="eventTZHour"
function-class="EsperPlugins"
function-method="eventTZHour"/>
</esper-configuration>
[1]: http://www.espertech.com/esper/release-5.2.0/esper-reference/html/extension.html#custom-singlerow-function from esper's docs

cannot return a utc datetime with moment.js

trying to pass a datetime object that has already been converted to UTC by momentjs to my MVC controller. I'm using a kendo datetimepicker, and for some reason, moment.utc just will not send the controller the value that i want. For example, i type in '9/17/2015 12:00 AM' into my kendo datetimepicker. I get this value like:
var start = $("#startTime").val();
which gives me "9/17/2015 12:00 AM" . great. then i convert this to a date object:
var t1 = new Date(start);
which reads as Thu Sep 17 2015 00:00:00 GMT-0700 (Pacific Daylight Time) {} . Still looking good. Next i try to convert to UTC with moment using:
var t2 = moment.utc(t1);
this gives me
dt {_isAMomentObject: true, _i: Thu Sep 17 2015 00:00:00 GMT-0700 (Pacific Daylight Time), _isUTC: true, _locale: fu, _d: Thu Sep 17 2015 00:00:0...
did NOT convert to utc. instead of converting all it seemed to do was take a date object and let me tell it 'hey, this is utc' and it said 'ok' (by flagging _isUTC:true), even though it's still saving the GMT value in there
even if i decided to make a moment object first, and then run UTC on it? it still comes out the same:
var t2 = moment(t1);
var t3 = moment.utc(t2);
So either of those values, if i run .format() on them (which is what i need to pass to my controller), i always get "2015-09-17T07:00:00+00:00", which is NOT the UTC time. It's the exact time i typed in. What am i doing wrong here?
A few things:
Don't even look at the fields prefixed with underscores. They're part of the internal design of moment.js, and not meant for direct consumption. In many cases, several of the fields have to be combined to get the correct results. This is accounted for in the functions of the public API, such as format.
Don't rely on the Date object to do your parsing. Results can be inconsistent across browsers. Moment has its own parser, which you can use like this:
moment("9/17/2015 12:00 AM","M/D/YYYY h:mm A")
However, in your particular case, you actually don't need to parse any string at all. You said you're using Kendo's DateTimePicker control, so you should use the value function, which already returns a Date object.
Moment has two different functions for working with UTC.
moment.utc(value) - interprets values in terms of UTC
m.utc() - where m is any moment instance, converts the value to UTC
Note that the second one it mutates the existing instance by switching it from "local mode" to "UTC mode". It also returns the instance if you want to chain functions, but it does modify the original instance as well.
You can use format after converting to UTC if you want the output to show the +00:00 offset. However, if you want to show Z (which is usually preferred), you you can just call .toISOString() without explicitly going to UTC first - since that function always outputs UTC.
Additionally, most modern browsers already support .toISOString() directly on the Date object, so unless you're targeting older browsers, you might not need moment at all.
You said "2015-09-17T07:00:00+00:00" was not the UTC time, but actually it is. You started with 00:00 in UTC-7, which is equivalent to 7:00 in UTC+0. So despite jumping through several unnecessary steps, moment still got it right in the end - at least with t3.
You can simplify your code with any of these:
var picker = $("#startTime").data().kendoDateTimePicker;
var dt = picker.value(); // dt is a Date object
var m = moment(dt); // m is a moment object
m.utc(); // m has been converted to UTC
var s = m.format(); // ex: "2015-09-17T07:00:00+00:00"
Or...
var picker = $("#startTime").data().kendoDateTimePicker;
var dt = picker.value(); // dt is a Date object
var m = moment(dt); // m is a moment object
var s = m.toISOString(); // ex: "2015-09-17T07:00:00Z"
Or...
var picker = $("#startTime").data().kendoDateTimePicker;
var dt = picker.value(); // dt is a Date object
var s = dt.toISOString(); // ex: "2015-09-17T07:00:00Z" (requires browser support)

rails group by utc date

I have a time field in table "timestamp without time zone". When record is saved to database, the utc time might be a different day compared to the local time. However, I need to group the records by date. Hence, I am doing something like this:
result = transmissions.joins(:report).where('reports.time::timestamp::date = ?', record.time.to_date)
The problem is if the utc date is on a different date than local time, then that record is not included in result. Any ideas how to get the right result?
And apparently I cannot change the "without time zone" either:
Rails database-specific data type
It says:
"concluded that the default ActiveRecord datetime and timestamp column types in schema migrations cannot be modified to force PostgreSQL to use timestamp with time zone."
So I have no idea how to group by date, as obviously something like this is wrong:
Unit.where(id: 1100).first.reports.order("DATE(time)").group("DATE(time)").count
=> {"2013-12-14"=>19, "2013-12-15"=>5}
That return value is completely wrong. All 25 records should be on 2013-12-14 and 0 records on 2013-12-15.
Assuming your records are timestamped with a particular UTC offset, you can try passing in the start and end times of the date in question in UTC format to your query:
result = transmissions.joins(:report).where('reports.time >= ? AND reports.time < ?', record.time.midnight.utc, (record.time.midnight + 1.day).utc)
Explanation:
midnight is a Rails method on an instance of Time that returns the Time object that represents midnight on the date of the original Time object. Similarly, record.time.midnight + 1.day returns the Time object representing midnight of the following day. Then, converting both Time objects – which are presumably timestamped in a standard UTC offset – to UTC creates a time period representing midnight-to-midnight for the system timezone in UTC format (not midnight in UTC time), which is precisely what you're seeking to query.
How about something like result = transmissions.joins(:report).where('reports.time >= ? AND reports.time <= ?', record.time.beginning_of_day.utc, record.time.end_of_day.utc)
The .utc part may not be necessary.

Grails Date Contraints

Im trying to add constraints to a user submitted text field, which is there Date of Birth. I need the user to be at least 18 but cant be over 113 years old.
final static Date MIN_DATE = new Date(Calendar.YEAR-18)
final static Date MAX_DATE = new Date(Calendar.YEAR-100)
static constraints = {
dob(nullabe: false, min: MIN_DATE, max: MAX_DATE
}
When I,
System.out.println('Year Max Date: ' + person.MAX_DATE)
it gives me,
Year Max Date: Wed Dec 31 17:59:59 CST 1969
and so does the min date.
I tried doing
final static Date MIN_DATE = new Date().getAt(Calendar.YEAR)-18
but that didn't work at all. I also tried doing it without the -18 and got the correct answer but it was 7 hours off. When I tried to relaunch the app from local it crashed it. And I cant recreate it ever since.
I have come to the understanding that the date its giving is the "Epoch" date or the date right before Unix was launched. Im just not sure how to get around it.
Any ideas/suggestions/concerns/experience with this problem?
Any Help would be greatly appreciated.
I don't agree with these constraints, firstly as they seem arbitrary (why should someone not be older than 100? And why can't I just lie if I am 16?)
And secondly, if the webserver is up for a couple of years, these static dates will slowly drift
But anyway (these concerns aside), I believe what you want is:
final static Date MIN_DATE = Calendar.instance.with { add( YEAR, -18 ) ; it }.time
final static Date MAX_DATE = Calendar.instance.with { add( YEAR, -100 ) ; it }.time
The issue is that Date constructor expects an argument representing milliseconds since January 1, 1970, 00:00:00 GMT. Since Calendar.YEAR is a constant that is used to represent the year field from a Calendar structure and equal to 1 you are getting the above values.
You need to convert the user submitted text field (a String) to a Date object with SimpleDateFormat. Then create a Calendar with the Date object and check the Year field.
Alternatively you can just parse the text field (String) yourself and pick the year, convert to int and do the check.

How does one create objects in the future but ignore yearly/annual TimeZone switches?

I create multiple scheduled objects with different scheduled_on attributes. For example, each object would have a date to land on 4:00pm the first of every month.
Once one of those objects hits a timezone change. The app intelligently configures it an hour ahead or behind so that its relative to its parent's timezone.
The problem is that the app will save an object as 4:00PM (in Pacific Standard) for times that will eventually be displayed as (PDT or an hour ahead or 5:00pm). This would mean that I need it to save an hour off in UTC so that when the time comes about, it will display as 4PM regardless of what timezone we are in.
Whats the best technique for ensuring this in Rails?
I'm going to answer this question by pointing out some good things to know about adding time in Rails in relation to timezone.
When you add time, time is allocated in UTC to stay the same time despite timezone changes :
t = Time.now
-> 2012-08-10 13:17:01 +0200
t + 90.days
-> 2012-11-08 13:17:01 +0100
A DateTime will not do this. A DateTime will go up an hour or down an hour in the same TimeZone it began in :
dt = DateTime.now
=> Fri, 10 Aug 2012 13:16:54 +0200
dt + 90.days
=> Thu, 08 Nov 2012 13:16:54 +0200
But a DateTime is the only way to get the number of days between two dates which you can do by subtracting two DateTimes. This, you can't do with a Time, because when substracting a time, it will not divide by a perfect 24 hours, you'll get an irrational number because of the timezone switch.
This is specific to my issue. But I solved my problem by converting my Time to DateTimes to find the number of days in distance, and then reconverted back to time to find a later time in UTC relative to a TimeZone change :
original_job.to_time + ( new_date.to_datetime - original_job.to_datetime ).to_i.days

Resources