I'm looking for advice on the best way to store event dates in Postgres when it comes to fetching them and displaying them on an calendar. I'm using an node/expressjs backend with postgres as a data store. On the front end I'm using vue with vuetify/nuxt. Vuetify has a lot of convenient UI components, but more specifically the v-calendar component:
V-Calendar
I've got a few edge cases that I'm having a hard time wrapping my head around.
I want to be able to fetch events for the current month from the database and events that spill over from one month to the next, and to the next, etc. What is the best way to do this? How should I model my database table and fetch the records (I'm using Postgres)? An event needs a name, start and end. Should I instead store the total duration of the event in a unix timestamp and query the events by range between a given month duration (in seconds)?
Any advice would be welcome.
Store your events with their beginning and end dates in a range type
You can then use the overlap && range operator to figure out which events belong on a certain month's calendar.
For instance, if you have an event with duration column of type daterange defined as '[2020-01-01, 2020-03-31]'::daterange, it will be match the following condition:
where duration && '[2020-02-01, 2020-03-01)'
Please note that the closing ) is deliberate since that excludes the upper limit from the range (in this case, 1 March).
In case you would rather not store the start and end dates inside a range type, you can always construct one on the fly:
where daterange(start_date, end_date, '[]') && '[2020-02-01, 2020-03-01)'
The range for the current month can be calculated on the fly:
select daterange(
date_trunc('month', now())::date,
(date_trunc('month', now()) + interval '1 month')::date, '[)'
);
daterange
-------------------------
[2020-07-01,2020-08-01)
(1 row)
Or for a three-month calendar:
select daterange(
(date_trunc('month', now()) - interval '1 month')::date,
(date_trunc('month', now()) + interval '2 month')::date, '[)'
);
daterange
-------------------------
[2020-06-01,2020-09-01)
(1 row)
The way we stored and retrieved events was that every time a user scrolls in the calendar i use a method to return start_date_time for the current month and the previous and next month. For a total of 3 months. This way we catch any calendar overlap. We use laravel in the backend, but you should be able to get the general gist of the method. Our tableify method just formats data for us.
My DB structure is as follows (removing subjective data):
CREATE TABLE calendar_events (
id bigserial NOT NULL,
calendar_event_category_id int4 NOT NULL,
name varchar(512) NOT NULL,
description text NULL,
start_date_time timestamp(0) NOT NULL,
end_date_time timestamp(0) NULL,
"data" json NULL,
user_id int4 NULL,
created_at timestamp(0) NULL,
updated_at timestamp(0) NULL,
CONSTRAINT calendar_events_pkey PRIMARY KEY (id),
CONSTRAINT calendar_events_calendar_event_category_id_foreign FOREIGN KEY (calendar_event_category_id) REFERENCES calendar_event_categories(id),
CONSTRAINT calendar_events_user_id_foreign FOREIGN KEY (user_id) REFERENCES users(id)
);
My index method:
public function index(Request $request)
{
$currentDate = empty($request->filter_date) ? Carbon::now() : new Carbon($request->filter_date);
if (! empty($request->filter_date)) {
return api_response('Retrieved Calendar Events.',
CalendarEvent::tableify($request,
CalendarEvent::where('start_date_time', '>=', $currentDate->subMonth(1)->isoFormat('YYYY-MM-DD'))->where('start_date_time', '<=', ($currentDate->addMonth(2)->isoFormat('YYYY-MM-DD')))->orderby('start_date_time', 'DESC')
)
);
} else {
return api_response('Retrieved Calendar Events.', CalendarEvent::tableify($request, CalendarEvent::orderby('start_date_time', 'DESC')));
}
}
That's the way I solved the overlap problem. Every time the user scrolls the frontend checks if a month was changed, if so, it updates the calendar with the latest 3 month chunk.
Related
I have a data table that I am trying to filter based on the date in one column. I would like to filter the data based on the lastModified column having a date one year or older but even getting it filter on some hard coded date would be a good start. The data in in string format so I am trying to use the new Date() function to convert to date.
var table = $('#database').DataTable( {
fixedHeader: true,
data: dataSet,
columns: [
{ data: "processName" },
{ data: "processLob" },
{ data: "processOwner"},
{ data: "RiskReviewer"},
{ data: "lastModified"}]
} );
var filteredData = table
.column( { data: "lastModified"} )
.data()
.filter( function ( value, index ) {
return new Date(value) < 2015-10-10 ? true : false;
} );
First thing you are going to want to do is add "columnDefs" object for your date column and specify it's type as "date". DateTables has built in date parsing as long as you are following a well known format. ColumnType API Def
If that doesn't get you there completely then you will want to define a render function for your data column on the new columnDef object you just created. There you can check the render type and return a "nice" value for display and raw data value (ideally a value of type Date) for everything else. Render API Defintion
Also some general advice don't try to fight the library. It actually is extremely flexible and can handle a lot. So use the built in API functions where ever possible. Usually things go awry when people try to manipulate the table manually using JQuery. Under the covers the DataTables plugin maintains a ton of state that never makes it to the DOM. Basically if there is a function in the API for it, use it.
EDIT: Adding answer to the original posters question even though he found another solution.
One thing to keep in mind is that "filter" is intended only to give you back a filtered data set. It will not change the display in the grid. Instead you will want to use "search" on the "column()" API item to filter the displayed rows in the DataTable.
There is a small problem with that however. The search method only accepts regular values not functions. So if you want to implement this you have supply a custom search function like so:
// The $.fn.dataTable.ext.search array is shared amongst all DataTables and
// all columns and search filters are evaluated in the order in which they
// appear in
// the array until a boolean value is returned.
$.fn.dataTable.ext.search.unshift(function(settings, data, dataIndex) {
// Using a negative value to get the column wraps around to the end of
// the columns so "-1" will always be your last column.
var dateColumn = $(this).column(-1);
// We get the data index of the dateColumn and compare it to the index
// for the column currently being searched.
if(dateColumn.index() !== dataIndex) {'
// Pretty sure this indicates to skip this search filter
return null;
}
var columnSearchingBy = $(this).column(dataIndex);
// Allows the data to be a string, milliseconds, UTC string format ..etc
var columnCellData = new Date(data.lastModified);
var valueToSearchBy = new Date(columnSearchingBy.search.value);
// Ok this is one of the worst named methods in all of javascript.
// Doesn't actually return a meaningful time. Instead it returns the a
// numeric value for the number of milliseconds since ~ 1970 I think.
//
// Kind of like "ticks()" does in other languages except ticks are
// measured differently. The search filter I am applying here is to
// only show dates in the DataTable that have a lastModified after or
// equal the column search.
return (valueToSearchBy.getTime() >= columnCellData.getTime());
});
// So this should use our fancy new search function applied to our datetime
// column. This will filter the displayed values in the DataTable and from
// that just a small filter on the table to get all the data for the rows
// that satisfy the search filter.
var filteredData = table
.column({ data: "lastModified"})
.search('2015-10-10')
.draw();
Even though you found another way to go on this one maybe the above will help you out later on.
Let's say I am building a hotel booking platform, and every Room record has availability calendar. A common search criteria is to search by duration. Where user inputs start date and end date, and database fetches rooms that are not occupied from that duration.
I have implemented a very naive approach where I store the occupied days as an array of days.
attribute :occupied_at_i do
array = []
if !occupied_at.empty?
occupied_at.each do |date|
array << Time.parse(date).to_i
end
end
array
end
And then at the client side, I add the following javascript code to cross-check if the day is in the numericRefinement
// Date Filters
$('.date-field')
.on('change', function() {
if(_.every(_.map($('.date-field'), function(date) {return _.isEmpty(date.value) }), function(n) {return n==false;})) {
helper.clearRefinements('occupied_at_i');
var arrayOfDates = addDateFilters();
_.forEach(arrayOfDates, function(n) {
helper.addNumericRefinement('occupied_at_i', '!=', n);
});
showClearAllFilters();
helper.search();
}
});
So, it obviously is not a good way to do it, I am wondering what's a better to leverage on algolia and search by duration?
Thanks
Hope this helps others (since question has asked long back)
it is always better do the filtering at server side and render the results. Here one approach would be to add datetime fields to Room model as start_date and end_date. So that when user inputs start date and end date, records are fetched based on occupied status in that duration. One simple query would be like:
Room.where.not("start_date >= ? AND end_date <= ?", start_date, end_date)
The other best solution would be like to have another model to save Room Bookings details with start and end datetime fields. This way we can have multiple room bookings for a particular room and it can be saved simultaneously in the room booking collection. Hence the query would become:
Room.left_joins(:room_bookings).
where(
"room_bookings.start_date > ? OR
room_bookings.end_date < ?", end_date, start_date
)
Okay, I have a date picker on my application. The date picker lets the user select the dates of the datasets they want to view in a table. I start off with getting the current ms data and send it to the backend. So, let's just say I send to the back end, the following dates:
{
start: '2014-12-1',
end: '2014-12-7'
}
The backend send the data within those dates back to the front end and store them in a javascript array, a front end cache if you want to call it that. Next the user selects the dates:
{
start: '2014-11-3',
end: '2014-11-9'
}
We retrieve the data that falls within the dates and store them in the same array as the above. Say the user then selects the following dates:
{
start: '2014-11-22',
end: '2014-11-29'
}
We store these in the array mentioned above again. The user then finally selects the date ranges
{
start: '2014-11-1',
end: '2014-12-31'
}
Since, I have already retrieved much of the data that is between these dates already, I would like to figure out a way to grab all the dates series that fit between the first three date series that are inside the fourth selection. That way I am only retrieving the data I don't already have. Is there any existing algorithm that I can implement in javascript that I could use to find all the date series that I am missing?
I'm building an application in Node.js and MongoDB, and the application has something of time-valid data, meaning if some piece of data was inserted into the database.
I'd like to remove it from the database (via code) after three days (or any amount of days/time spread).
Currently, my solution is to have some sort of member in my Schema that checks when it was actually posted and subsequently removes it when the current time is past 3 days from the insertion, but I'm having trouble in figuring out a good way to write it in code.
Are there any standard ways to accomplish something like this?
There are two basic ways to accomplish this with a TTL index. A TTL index will let you define a special type of index on a BSON Date field that will automatically delete documents based on age. First, you will need to have a BSON Date field in your documents. If you don't have one, this won't work. http://docs.mongodb.org/manual/reference/bson-types/#document-bson-type-date
Then you can either delete all documents after they reach a certain age, or set expiration dates for each document as you insert them.
For the first case, assuming you wanted to delete documents after 1 hour you would create this index:
db.mycollection.ensureIndex( { "createdAt": 1 }, { expireAfterSeconds: 3600 } )
assuming you had a createdAt field that was a date type. MongoDB will take care of deleting all documents in the collection once they reach 3600 seconds (or 1 hour) old.
For the second case, you will create an index with expireAfterSeconds set to 0 on a different field:
db.mycollection.ensureIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )
If you then insert a document with an expireAt field set to a date mongoDB will delete that document at that date and time:
db.mycollection.insert( {
"expireAt": new Date('June 6, 2014 13:52:00'),
"mydata": "data"
} )
You can read more detail about how to use TTL indexes here:
http://docs.mongodb.org/manual/tutorial/expire-data/
I am using the DXTREME framework from Devexpress to connect a HTML mobile app to an OData source.
One of my tables in SQL Server, exposed through the OData service is a table with a date (not datetime) field in it. It is exposed through OData like this:
<d:TaskDate m:type="Edm.DateTime">2010-04-01T00:00:00</d:TaskDate>
I am trying to filter the data on this field through a calendar control, but when I try to filter the datasource on the JS side, I get no matches. This is because the date is passed to the OData service, I believe, in UTC format, so if I query for TaskDate = '10/JUL/2013', I believe the date is passed as "09/JUL/2013 14:00". If I filter on TaskDate > '10/JUL/2013' I get results back from after "09/JUL/2013 14:00" at any rate.
I have tried declaring a new date with no time part:
filterDate = new Date(2013, 6, 10)
but is still doesn't work, it still subtracts 10 formy time zone on the JS side.
What I want to do is to return a lists of Tasks valid on that particular date. How can I achieve this?
I think my problem was the confusion around the dxDateBox control returning just a date, and that date being changed when passed to my odata service.
I solved the issue by converting the date to UTC myself, but just using the Date parts from the control, (where filterDate came from the control):
var paramDate = new Date(Date.UTC(this.filterDate().getFullYear(), this.filterDate().getMonth(), this.filterDate().getDate()));
this.dataSource.filter(["TaskDate", "=", paramDate]);
This works nicely, but seems rather verbose.