Issue with dates in Telerik's grid (admin)

9 years ago
Hi,

I'm using NopCommerce 2.3, slighty modified but no modifications in direct relation with issue. I'm using SQL Server 2008 Express R2.

I'm experiencing an issue with the display of DateTimes in telerik's ajax grid. My store's time zone is configured as EST (eastern UTC -5). My web server's time zone is PST (pacific UTC -8). I oly use the store time, customers can't set their own timezone. For instance, I have an order created on 19-12-2011 4:08 AM UTC that shows as created on 19-12-2011 2:08 AM (store time) in the grid. I'm expecting to see 18-12-2011 11:08 PM (store time).

There's a thread in telerik's forums explaining this problem.

In my case, I made the following calculations to the "wrong" date in th grid:
19-12-2011 4:08 UTC > user (store) time : -5h = 18-12-2011 11:08 EST
18-12-2011 11:08 EST serialized (must be in utc, so converted from local to utc, but using 18-12-2011 11:08 EST as local) : +8h = 19-12-2011 7:08 UTC
19-12-2011 7:08 UTC converted to client's local time (telerik) : -5h = 19-12-2011 2:08 EST (wrong)

I temporarily took their solution to convert it back to utc on client side and then reapply the webserver's utc offset (-8) to get to the good date/time. This allowed me to only modify the code in the views and not in the controllers.
9 years ago
On what admin page are you getting this error?
9 years ago
And what browser are you using?
9 years ago
I got the error on the orders' list page (Sales > Orders in admin menu) but I think the issue exists wherever there's a telerik grid with a date in it (ex logs). I am using IE9 but could also experience the issue in the latest chrome).
9 years ago
It works fine on my machine, but I see this issue on the admin demo site. I'll investigate it.

Thanks for reporting
9 years ago
The issue will only show up when there's a difference in the client's (browser) timezone and the server's timezone.
9 years ago
Hi Andrei,

Did you check this issue?

I should create a fork and add my contributions there from my local cloned repository but since I haven't done that yet, here's my fix:

In _AdminLayout.cshtml, in the server side code at the top I added:


    var delta = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).TotalHours;


and then in the head section:

<script type="text/javascript">
        function toServerTime(date)
        {
            var timeAdjustment = @delta;
            var utcDate = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
            // offset measured in milliseconds
            var serverOffset = timeAdjustment * 60 * 60 * 1000;
            var serverTime = new Date(utcDate.getTime() + serverOffset);
            return $.telerik.formatString("{0:G}", serverTime);
         }
    </script>


Then whenever you want to convert a time in a grid, use (ex. with CreatedOn property):

columns.Bound(x => x.CreatedOn).ClientTemplate("<#= toServerTime(CreatedOn) #>");
9 years ago
asoares wrote:
Did you check this issue?

Yes. But I still did not find a good solution. Your one also doesn't work well for all scenarios. When setting delta value in your code you're using current datetime. It won't work for old dates. I described it with some example on Telerik forums here (seems that their forums don't work right now)

Maybe, the most correct way will be returning string value of dates. But it's not very elegant.
9 years ago
I think I have a solution.

In every controller method that returns date time objects, instead of returning a JsonResult, do:


JavaScriptSerializer js = new JavaScriptSerializer();
js.RegisterConverters(new List<JavaScriptConverter> { new DateTimeJavaScriptConverter(_dateTimeHelper) });

string output = js.Serialize(gridModel);

//Add backslashes and single quotes to date in serialized output (it cant be done in the converter because the uri gets url encoded)
var modifiedOutput = System.Text.RegularExpressions.Regex.Replace(output, "/Date\\((.*?)\\)/", "\\/Date('$1')\\/");

return Content(modifiedOutput, "application/json");


The DateTimeJavaScriptConverter is (slighty modified from this post):


public class DateTimeJavaScriptConverter : JavaScriptConverter
        {
            private readonly IDateTimeHelper _dateTimeHelper;

            public DateTimeJavaScriptConverter(IDateTimeHelper dateTimeHelper)
            {
                _dateTimeHelper = dateTimeHelper;
            }

            public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
            {
                return new JavaScriptSerializer().ConvertToType(dictionary, type);
            }

            public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
            {
                if (!(obj is DateTime)) return null;
                var date = (DateTime)obj;
                var dateOffset = new DateTimeOffset(date, _dateTimeHelper.CurrentTimeZone.GetUtcOffset(date));

                return new CustomString("/Date(" + dateOffset.ToString("yyyy-MM-ddTHH:mm:sszzz") + ")/");
            }

            public override IEnumerable<Type> SupportedTypes
            {
                get { return new[] { typeof(DateTime) }; }
            }

            private class CustomString : Uri, IDictionary<string, object>
            {
                public CustomString(string str)
                    : base(str, UriKind.Relative)
                {
                }

                void IDictionary<string, object>.Add(string key, object value)
                {
                    throw new NotImplementedException();
                }

                bool IDictionary<string, object>.ContainsKey(string key)
                {
                    throw new NotImplementedException();
                }

                ICollection<string> IDictionary<string, object>.Keys
                {
                    get { throw new NotImplementedException(); }
                }

                bool IDictionary<string, object>.Remove(string key)
                {
                    throw new NotImplementedException();
                }

                bool IDictionary<string, object>.TryGetValue(string key, out object value)
                {
                    throw new NotImplementedException();
                }

                ICollection<object> IDictionary<string, object>.Values
                {
                    get { throw new NotImplementedException(); }
                }

                object IDictionary<string, object>.this[string key]
                {
                    get
                    {
                        throw new NotImplementedException();
                    }
                    set
                    {
                        throw new NotImplementedException();
                    }
                }

                void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
                {
                    throw new NotImplementedException();
                }

                void ICollection<KeyValuePair<string, object>>.Clear()
                {
                    throw new NotImplementedException();
                }

                bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
                {
                    throw new NotImplementedException();
                }

                void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
                {
                    throw new NotImplementedException();
                }

                int ICollection<KeyValuePair<string, object>>.Count
                {
                    get { throw new NotImplementedException(); }
                }

                bool ICollection<KeyValuePair<string, object>>.IsReadOnly
                {
                    get { throw new NotImplementedException(); }
                }

                bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
                {
                    throw new NotImplementedException();
                }

                IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
                {
                    throw new NotImplementedException();
                }

                IEnumerator IEnumerable.GetEnumerator()
                {
                    throw new NotImplementedException();
                }
            }
        }


Let me know what you think of this solution. The conversion is now done on the server for each date.
9 years ago
Thanks.
I'm a big fan of keep it simple principle. I'm not sure but it looks more like a hack. I think it should be well tested in order to find any hidden pitfalls. Furthermore, new developers just won't understand why it's used. Personally I think about returning string representation of dates (it's more simple)