bootstrap-year-calendar icon indicating copy to clipboard operation
bootstrap-year-calendar copied to clipboard

startDate and endDate in datasource look incorrect but in calendar they are intepreted fine

Open PrinceOfAbyss opened this issue 7 years ago • 6 comments

For debugging purpose while setting up the plugin, I use a temporary div whose html I update by the calendar's datasource. So, at first I was confused with the startDate and endDate 's shown in the datasource, but to my surprise they are depicted fine. So, is this intended behavior, or do I have to add one day to the starting and ending day of each period? My concern is when on the server-side I'll be storing those periods (haven't gotten there yet). How will PHP interpret those "startDate":"2015-12-31T22:00:00.000Z","endDate":"2016-01-30T22:00:00.000Z" into the correct period which is 1/1/2016 - 31/1/2016?

Please, have a look at the image below to better understand what I mean:

datasource

PrinceOfAbyss avatar Aug 27 '16 04:08 PrinceOfAbyss

There are a few difficulties working with dates on the client side and on the server side. As you already learned, the date formats are different, but luckily there is a nice easy way of dealing with them that works in all browsers.

First, this calendar startDate and endDate take actual javascript date objects. This mean that even if you store date in a string like so Sun Aug 28 2016 15:08:02 GMT+1000 (AEST) in your DB, it will not work. For instance, you will have to pass new Date(2016,28,8) to that property to display for it to display a date on the calendar.

Now, about PHP. I am using Laravel as my backend, and here is what I do to retrieve data from my DB and display it on the calendar:

$.get('api/show', function (data) {
        // store the response into the object
        return dateObject = data;
    })
    .done(function(){
        // Define an object which will be fed into the calendar options object
        var dates = [];
        // Go through each of the elements in the dateObject and modify
        // each of the elements according to the calendar's object pattern:
        // {
        //   id
        //   name
        //   location
        //   startDate
        //   endDate
        //   color
        // }

        for(var i = 0; i < dateObject.length; i++){
            var eachElement = {};
            eachElement.startDate = new Date(dateObject[i].date_start);
            eachElement.endDate = new Date(dateObject[i].date_end);
            eachElement.color = dateObject[i].color;
            dates.push(eachElement);
        }

Now that I have the data sorted and correctly stored in the dates object, I simply pass it to the calendar's dateSource.

The date that is returned from the server is stored in the this format 2016-02-01, which I then simply pass to JS new Date(), like so new Date(2016-02-01), as you can see in the loop example above or here: new Date(dateObject[i].date_start).

Sometimes you will have formatting issues where you will have to make sure your date format is exactly like you want it. That will most likely happen when you need to send data to the server, in which case I use this bit of code I came across on StackOverflow:

// PHP/MYSQL dates to JAVASCRIPT DATE OBJECT conversion
// var t = "2010-06-09 13:12:01".split(/[- :]/);
// var d = new Date(Date.UTC(t[0], t[1]-1, t[2], t[3], t[4], t[5]));

Using regex you simply break the string into year, day, month, hour, minute and the second and use those however you like.

Also I would recommend using momentjs library, it is great.

Lastly, I am going to copy paste my code for the $.post ajax request to store data from selection to the calendar, in case you might find it useful:

function insertDate (startDate, endDate, color){
        $.post('api/store', { startDate: startDate, endDate: endDate, color: color }, function(data) {
            console.log(data);
            var dataSource = $('#calendar').data('calendar').getDataSource();
            console.log(dataSource);
            var newEvent = {
                startDate: new Date(startDate),
                endDate: new Date(endDate),
                color: color
            };
            dataSource.push(newEvent);
            console.log
            $('#calendar').data('calendar').setDataSource(dataSource);
            updateAllDates(dataSource);
        })
        .done(function(){

        });
    }

What I do here is just send startDate, endDate and the colour to my api/store route and that stores all the information I provided in the dates table.

Quick note: You will have issues with your month and day not being the one you need, but the one before it. That is because JS dates start with 0, instead of 1 for January and sometimes the same for days.

So again, use regex to fix it like so:

            var startDateFromInput = $('#startDateInput').val().split(/[-]/);
            var startDate = new Date(startDateFromInput[0], startDateFromInput[1]-1, startDateFromInput[2]);

Hope this helps :)

noncototient avatar Aug 28 '16 05:08 noncototient

Oh yeah, and I noticed that when you have blue background for your dates on your calendar you have the font display as white. Did you do this yourself or am I missing this feature of this plugin?

noncototient avatar Aug 28 '16 05:08 noncototient

WOW, thanks for the analysis and even for having the kindness to share your code. I'm sure I'll need some parts of it when I implement the DB storage and retrieval part of my project.

However, unless I missed the explanation in your answer, I still don't quite understand why the JS dates returned miss one day (actually two hours) on each side of the period. Ie. for the period 1/1/2016 - 1/31/2016, the JS dates returned by the plugin are startDate: 2015-12-31T22:00:00.000Z, which is off by 2 hours the date it should naturally return (1/1/2016), and endDate: 2016-01-30T22:00:00.000Z, which is again off by 2 hours before the date it should return (1/31/2016). Where do those two hours come from? Is it a timezone difference because I'm on UTC+2? Or, does it take the timezone of the server into consideration? Or, is it something else? That's exactly what I don't understand.

As for the font color part, no it's not a feature you missed. It's a feature that's missing from the plugin. But I used to little functions that calculate the color for me, which then I make use of in customDataSourceRenderer().

    function hex2rgb(hex) {
        var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        hex = hex.replace(shorthandRegex, function(m, r, g, b) {
            return r + r + g + g + b + b;
        });
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    }
    var blackORwhite = function(rgb) {
        var brightness = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 255000;
        if (brightness >= 0.5) {
            return '#000';
        } else {
            return '#fff';
        }
    }
//////////////////////////////////
//////////////////////////////////
            customDataSourceRenderer: function(element, date, events) {
                $(element).parent().css('background-color', events[events.length - 1].color);
                $(element).css('color', blackORwhite(hex2rgb(events[events.length - 1].color)))
                if (events[events.length - 1].startDate.getTime() == events[events.length - 1].endDate.getTime() && events[events.length - 1].startDate.getTime() == date.getTime()) {
                    $(element).parent().addClass('round-both');
                } else {
                    if (events[events.length - 1].startDate.getTime() == date.getTime()) {
                        $(element).parent().addClass('round-left');
                    } else
                    if (events[events.length - 1].endDate.getTime() == date.getTime()) {
                        $(element).parent().addClass('round-right');
                    }
                }
            },

PrinceOfAbyss avatar Aug 28 '16 17:08 PrinceOfAbyss

I just realized another issue that comes to add to my idea that there has to be a problem with startDate. Take a look at this:

        $('.input-daterange input').each(function() {
            $(this).datepicker({
                startDate: new Date(moment()),
                endDate: new Date(moment().add(10, 'days')),
                language: lang,
                autoclose: true
            });
        });
        $('#calendar').calendar({
            minDate: new Date(moment()),
            maxDate: new Date(moment().add(10, 'days')),
            enableRangeSelection: true,
            enableContextMenu: true,
//////////////////////
//////////////////////
            language: lang,
            dataSource: [datasource]
        });

These are the initialization functions of bootstrap-year-calendar and date-picker plugins. Notice the minDate and maxDate for year calendar, whose respective properties for date picker are startDate and endDate.

One would expect they'd produce the same boundaries, right? Well they don't. So currently, 00:38 - 08/29/2016 for my timezone, year calendar has enabled the dates from 30 of this month to 08 of Sepetember, while date picker has 29 of this month to 08 of Sepetember.

Take a look at the screenshots below as a proof of my word.

byc

dpstart

dpend

PrinceOfAbyss avatar Aug 28 '16 21:08 PrinceOfAbyss

I still don't quite understand why the JS dates returned miss one day (actually two hours) on each side of the period.

Not 100% sure why, but I suspect its because of the timezone settings.

As for the font color part, no it's not a feature you missed

Thanks for this! Saw this solution on StackOverflow too, but ended up just making all marked days (ones with dates) with white font and it will do for now.

But I used to little functions that calculate the color for me, which then I make use of in customDataSourceRenderer()

About this... what frontend framework are you working with? Or are you just using jQuery? I was wondering if I could use Vue.js and its reactivity with this plugin to update calendar dates without actually calling setDataSource(updatedDataSource). Since vue.js has two way data binding, it would be cool if it worked. Haven't had time to actually test it.

One would expect they'd produce the same boundaries, right? Well they don't. So currently, 00:38 - 08/29/2016 for my timezone, year calendar has enabled the dates from 30 of this month to 08 of Sepetember, while date picker has 29 of this month to 08 of Sepetember.

I may be misunderstanding you, but like I said previously, this calendar counts months starting with 0. So January would month 0, not 1. Thus, August would be month 7 and September would be month 8.

Is this what you're asking?

noncototient avatar Aug 30 '16 02:08 noncototient

Not sure if you have resolved the issue. If not, there is function doing the local time conversion for you:

e.startDate.toLocaleDateString() => returns correct local date, which will remove those hour offset for you.

pch4115209 avatar Sep 06 '17 02:09 pch4115209