How can I add timezone to Esper queries? - timezone

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

Related

Postgres at time zone '+07' syntax [duplicate]

I am running PostgreSQL 9.6.6 on x86_64-pc-linux-gnu and my time zone is set to 'UTC'.
Does anyone know why the results of the following SELECT statements are different?
A)
SELECT timezone('EST', '2017-12-21');
timezone
---------------------
2017-12-20 19:00:00
B)
SELECT timezone('-05', '2017-12-21');
timezone
---------------------
2017-12-21 05:00:00
According to the pg_timezone_names table -05 should have the same offset as EST... Any thoughts? Thanks.
https://www.postgresql.org/docs/current/static/view-pg-timezone-names.html
The view pg_timezone_names provides a list of time zone names that are
recognized by SET TIMEZONE
and further:
utc_offset interval Offset from UTC (positive means east of Greenwich)
when you set timezone to 'EST' - you declare that your client is in EST time zone, thus returned time will be adjusted for your tz:
t=# select '2017-12-21'::timestamptz;
timestamptz
------------------------
2017-12-21 00:00:00-05
(1 row)
the interval match utc_offset from pg_timezone_names and isequal -05, so it works as expected. (indeed in EST will be 5 hours less then UTC) same result if you set timezone to '-05'.
Both -05 and EST give same result for SET TIMEZONE as described in docs.
Now you answer reconciles with docs on using interval: https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-ZONECONVERT
In these expressions, the desired time zone zone can be specified
either as a text string (e.g., 'PST') or as an interval (e.g.,
INTERVAL '-08:00').
following these rules it works as well:
t=# select '2017-12-21'::timestamptz at time zone 'EST';
timezone
---------------------
2017-12-20 19:00:00
(1 row)
t=# select '2017-12-21'::timestamptz at time zone interval '-05:00';
timezone
---------------------
2017-12-20 19:00:00
(1 row)
but further, docs say:
In the text case, a time zone name can be specified in any of the ways
described in Section 8.5.3.
which is https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-TIMEZONES
PostgreSQL allows you to specify time zones in three different forms:
recognized time zone names are listed in the pg_timezone_names
recognized abbreviations are listed in the pg_timezone_abbrevs
POSIX-style time zone specifications of the form STDoffset or STDoffsetDST
(formatting mine)
and lastly:
One should be wary that the POSIX-style time zone feature can lead to
silently accepting bogus input...Another issue to keep in mind is that
in POSIX time zone names, positive offsets are used for locations west
of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601
convention that positive timezone offsets are east of Greenwich.
TL;DR
So in short - when you define '-05' as text (not interval) input for timezone() function or AT TIME ZONE directive (effectively same) Postgres thinks this is an attempt to use POSIX style time zone and thus inverts sign, thus you get "opposite" result...
a simple demonstration of this documented inversion:
t=# select '2017-12-21'::timestamptz at time zone '05';
timezone
---------------------
2017-12-20 19:00:00
(1 row)
Okay I think I found an answer to my own question:
According to the PostgreSQL docs, section 9.9.3 at the following link https://www.postgresql.org/docs/9.6/static/functions-datetime.html
In these expressions, the desired time zone zone can be specified either as a text string (e.g., 'PST') or as an interval (e.g., INTERVAL '-08:00'). In the text case, a time zone name can be specified in any of the ways described in Section 8.5.3.
So using the INTERVAL syntax, the following appears to work:
SELECT timezone(INTERVAL '-05:00', '2017-12-21');
timezone
---------------------
2017-12-20 19:00:00
I think it is still curious, what exactly SELECT timezone('-05', '2017-12-21'); means, as the following also provides the expected result (with the addition of a TZ offset):
SELECT timezone('-05', '2017-12-21'::timestamp);
timezone
------------------------
2017-12-20 19:00:00+00

Get records created after a particular time of day

Say I have an Event model with a date_time field representing the date time the event is held, and I want to see all Events that are held, say, 'after 10pm', or 'before 7am' across multiple dates. How could I do this?
My first thought was something like this:
scope :after_time ->(time){ where("events.date_time::time between ?::time and '23:59'::time", time) }
But this doesn't work because dates are stored in UTC and converted to the app's timezone by ActiveRecord.
So let's say I'm searching for Events after 5pm, from my local Adelaide time. The eventual query is this:
WHERE (events.date_time::time between '2016-10-09 06:30:00.000000'::time and '23:59'::time)
That is, because my timezone is +10:30 (Adelaide time), it's now trying to calculate between 6:30am and midnight, where it really needs to be finding ones created between 6:30am and 1:30pm utc.
Now, for this example in particular I could probably hack something together to work out what the 'midnight' time needs to be given the time zone difference. But the between <given time> and <midnight in Adelaide> calculation isn't going to work if that period spans midnight utc. So that solution is bust.
UPDATE:
I think I've managed to get the result I want by trial and error, but I'm not sure I understand exactly what's going on.
scope :after_time, ->(time) {
time = time.strftime('%H:%M:%S')
where_clause = <<-SQL
(events.date_time at time zone 'UTC' at time zone 'ACDT')::time
between ? and '23:59:59'
SQL
joins(:performances).where(where_clause, time)
}
It's basically turning everything into the one time zone so the query for each row ends up looking something like WHERE '20:30:00' between '17:00:00' and '23:59:59', so I'm not having to worry about times spanning over midnight.
Even still, I feel like there's probably a proper way to do this, so I'm open to suggestions.
Check if this works for you,
s = DateTime.now.change(hour: 6, min: 30).utc
e = Date.today.end_of_day.utc
Event.where("date_time::time between ?::time and ?::time", s, e)
this may help you and then you need not to convert every date of DB, instead you can convert the parameterized timestamp into UTC time:
scope :after, ->(start_time) { where('created_at::time > :time', time: start_time.utc.strftime('%H:%M:%S')) }
Now,
for e.g. I do have 3 events for following timestamps(all in UTC):
2013-04-11 11:43:43
2013-04-11 15:10:40
2013-04-12 07:39:26
and then you can call:
start_time = Time.zone.parse('2016-01-01 20:00:00')
# => Fri, 01 Jan 2016 20:00:00 ACDT +10:30
Event.after(start_time) # this will return 2 events(1, 2)
query will be:
SELECT "events".* FROM "events" WHERE (created_at::time > '09:30:00')
Note: This will raise an error ActiveRecord::StatementInvalid: PG::AmbiguousColumn: ERROR: column reference "created_at" is ambiguous if you will use this query with any another model that will have created_at column

TIMESTAMP WITHOUT TIME ZONE, INTERVAL and DST extravaganza

I'm working on a Rails application which stores all dates to PostgreSQL as "TIMESTAMP WITHOUT TIME ZONE". (Rails handles the time zone on the application layer which for this application is "Europe/Berlin".) Unfortunately, Daylight Savings Time (DST) becomes an issue.
The simplified "projects" table has the following columns:
started_at TIMESTAMP WITHOUT TIME ZONE
duration INTEGER
Projects start at started_at and run for duration days.
Now, say there's only one project which starts on 2015-01-01 at 10:00. Since this is "Europe/Berlin" and it's January (no DST), the record looks like this on the database:
SET TimeZone = 'UTC';
SELECT started_at from projects;
# => 2015-01-01 09:00:00
It should end on 2015-06-30 at 10:00 (Europe/Berlin). But it's summer now, so DST applies and 10:00 in "Europe/Berlin" is now 08:00 in UTC.
Due to this, finding all projects for which the duration has elapsed by use of the following query does not work for projects which start/end across DST boundaries:
SELECT * FROM projects WHERE started_at + INTERVAL '1 day' * duration < NOW()
I guess it would be best if the above WHERE did the calculation in timezone "Europe/Berlin" rather than "UTC". I've tried a few things with ::TIMESTAMTZ and AT TIME ZONE none of which has worked.
As a side note: According to the PostgreSQL docs, + INTERVAL should deal with '1 day' intervals differently from '24 hours' intervals when it comes to DST. Adding days ignores DST, so 10:00 always stays 10:00. When adding hours on the other hand, 10:00 may become 09:00 or 11:00 if you cross the DST boundary one way or another.
Thanks a lot for any hints!
I think you've got two strategies for avoiding headache:
Let Rails handle everything to do with Timezones, so Postgres doesn't have to at all
or
Let Postgres handle everything to do with Timezones, so Rails doesn't have to at all
Mixing the two will always be a pain, and is basically what's causing your problems now. I'd go with strategy 1 (let Rails handle it). To do this, your Postgres database should store a start time, and a finish time, both in UTC. duration may be a thing in your user interface still, but if a user enters a start time and a duration, then you should calculate a finish time, and store that finish time in your database. The start time the users enters, and the finish time that you calculate in your app, with both be timezone-specific, and you just let Rails handle the conversion to UTC when it saves to the database.
Your query would then be simply:
SELECT * FROM projects WHERE finished_at < NOW()
(BTW, You could also store the duration in your database, but it's superfluous, since it can be calculated from the start time and finish time)
I've created a function which calculates ended_at by adding duration days to started_at honoring DST changes of a given time zone. Both started_at and ended_at, however, are in UTC and therefore play nice with Rails.
It turns started_at (timestamp without time zone, implicit UTC by Rails) to a timestamp with time zone UTC, then to the given time zone, adds the duration and returns the timestamp without time zone (implicit UTC).
# ended_at(started_at, duration, time_zone)
CREATE FUNCTION ended_at(timestamp, integer, text = 'Europe/Zurich') RETURNS timestamp AS $$
SELECT (($1::timestamp AT TIME ZONE 'UTC' AT TIME ZONE $3 + INTERVAL '1 day' * $2) AT TIME ZONE $3)::timestamp
$$ LANGUAGE SQL IMMUTABLE SET search_path = public, pg_temp;
With this function, I can omit having to add ended_at as an explicit column which would have to be kept in sync. And it's easy to use:
SELECT ended_at(started_at, duration) FROM projects

Timezone Offset in Angular JS and 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.

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.

Resources