21

Update: this is only an issue with a concrete type in a custom class. I implemented the same pattern with the Contact sObject containing a DateTime field, no exception thrown. Code below and the question title updated to demonstrate this difference in behavior.


I am using @RemoteAction methods to expose a custom Apex Type to the page. When performing the retrieve operation, the DateTime members on the class are serialized into the unix epoch format. Like so:

{
    name: "Item Name",
    theDate: 1440797362525
}

However, when performing a commit back to the controller with this exact same data, the unix epoch format is not able to be deserialized back into the concrete type and it throws an exception.

Visualforce Remoting Exception: Unable to convert date '1440797362525' to Apex type Datetime.

This is "JS Remoting: 101" and it frustrates me that it does not work properly.

Questions:

  1. What is the correct way to implement this very simple pattern?
  2. Why does this work properly with an sObject but not with an apex type?

Controller

public with sharing class DateTimeSerializationController {

    public class MyItem {
        public String name      { get; set; }
        public DateTime theDate { get; set; }
    }

    @RemoteAction
    public static MyItem fetchTheItem() {
        MyItem item = new MyItem();
        item.name = 'Item Name';
        item.theDate = DateTime.now();
        return item;
    }

    @RemoteAction
    public static MyItem storeTheItem(MyItem item) {
        // DML or something similar could happen here
        system.debug(item);

        return item;
    }

    @RemoteAction
    public static Contact fetchTheContact() {
        Contact item = new Contact();
        item.FirstName = 'Joe';
        item.LastName = 'Bob';
        item.My_Date__c = DateTime.now();
        return item;
    }

    @RemoteAction
    public static Contact storeTheContact(Contact item) {
        // DML or something similar could happen here
        system.debug(item);

        return item;
    }
}

VF Page

<apex:page controller="DateTimeSerializationController">
    <script>
        window.myApp = window.myApp || {};

        window.myApp.fetchItem = function() {

            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.fetchTheItem',
                function(result, event){
                    if (event.status) {
                        console.log(result);
                        myApp.myItem = result;    
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );

        }

        window.myApp.storeItem = function() {
            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.storeTheItem',
                myApp.myItem,
                function(result, event){
                    if (event.status) {
                        console.log(result);
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );
        }

        window.myApp.fetchContact = function() {

            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.fetchTheContact',
                function(result, event){
                    if (event.status) {
                        console.log(result);
                        myApp.myContact = result;    
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );

        }

        window.myApp.storeContact = function() {
            Visualforce.remoting.Manager.invokeAction(
                'DateTimeSerializationController.storeTheContact',
                myApp.myContact,
                function(result, event){
                    if (event.status) {
                        console.log(result);
                    } else {
                        console.log(event.message);
                    }
                }, 
                {escape: true}
            );
        }
    </script>

    <p>Open the browser console and then click the buttons.</p>

    <button onclick="myApp.fetchItem();">Fetch Item</button>
    <button onclick="myApp.storeItem();">Store Item</button>

    <button onclick="myApp.fetchContact();">Fetch Contact</button>
    <button onclick="myApp.storeContact();">Store Contact</button>
</apex:page>
  • 4
    In some API 25 code I was surprised that I had to explicitly use toUTCString on dates going from JavaScript to Apex Date fields and explicitly use new Date() on the number coming from Apex back into JavaScript. Looks like that may still be the case. – Keith C Aug 28 '15 at 22:05
  • This is especially maddening because neither Datetime methods nor JSON.deserialize recognize the long format of "Wed, 14 Jun 2017 07:00:00 GMT". – David Cheng Jun 11 '18 at 18:31
14
+200

To send a DateTime field defined on a custom Apex class from JavaScript to Apex using Visualforce Remoting you need to use toUTCString().

You can create a JavaScript Date object from a Unix time value using the Date(value) constructor.

If you change your storeItem function to the following then everything should work without you needing complicated parsing logic in your controller or additional fields on your object.

It's not ideal, especially if you have a large number of DateTime fields on your object but it gives you a way forward at least.

window.myApp.storeItem = function() {
    myApp.myItem.theDate = new Date(myApp.myItem.theDate).toUTCString();

    Visualforce.remoting.Manager.invokeAction(
       'DateTimeSerializationController.storeTheItem',
        myApp.myItem,
        function(result, event){
            if (event.status) {
                console.log(result);
            } else {
                console.log(event.message);
            }
        }, 
        {escape: true}
    );
}

As to the second part of your question, I have no idea why this behaviour is different for Apex classes vs. sObject fields.

That said, sObject fields have a number of special properties (such as being able to be used with apex:inputField) so until I have a better answer I'd have to consider this a 'special' feature/property of sObjects.

  • This is great idea. There is only one issue (although very minor )with this approach this approach. When you pass 1441609310569 to VF and when you get return you get 1441609310000. So basically you are loosing milliseconds part of your date time variable. In this case 569 milliseconds If your application is not concern about it then i would suggest this approch is better. – AtulRajguru9 Sep 7 '15 at 7:06
  • Chosen as the answer because this is the least invasive solution. SFDC really needs to correct the underlying issue. Thanks, Alex! – Mark Pond Sep 7 '15 at 15:46
  • not sure why, but for me that doesn't exactly work. What I had to do is actually get the date or datetime (for me it was just a date) as ticks... (through the Date.getTime() function) – KoenVM Aug 12 '16 at 14:23
3

I think this is bug in serialization and de-serialization process. One work around for this could be define 2 fields. One as String and other as DateTime. while setting set String field and then convert it into Date field when required.

Here is What I mean:

<apex:page controller="DateTimeSerializationController">
<script>
    window.myApp = window.myApp || {};

    window.myApp.fetchItem = function() {

        Visualforce.remoting.Manager.invokeAction(
            'DateTimeSerializationController.fetchTheItem',
            function(result, event){
                if (event.status) {

                    myApp.myItem = result;
                    myApp.myItem.theDateS = ''+myApp.myItem.theDate;   //Copy the value in String
                    myApp.myItem.theDate = null;                       //Set the DateTime as null Or you will see error
                    console.log(myApp.myItem);
                } else {
                    console.log(event.message);
                }
            }, 
            {escape: true}
        );

    }

    window.myApp.storeItem = function() {
    console.log(myApp.myItem);
        Visualforce.remoting.Manager.invokeAction(
            'DateTimeSerializationController.storeTheItem',
            myApp.myItem,
            function(result, event){
                if (event.status) {
                    console.log(result);
                } else {
                    console.log(event.message);
                }
            }, 
            {escape: true}
        );
    }

    window.myApp.fetchContact = function() {

        Visualforce.remoting.Manager.invokeAction(
            'DateTimeSerializationController.fetchTheContact',
            function(result, event){
                if (event.status) {
                    console.log(result);
                    myApp.myContact = result;    
                } else {
                    console.log(event.message);
                }
            }, 
            {escape: true}
        );

    }

    window.myApp.storeContact = function() {
        Visualforce.remoting.Manager.invokeAction(
            'DateTimeSerializationController.storeTheContact',
            myApp.myContact,
            function(result, event){
                if (event.status) {
                    console.log(result);
                } else {
                    console.log(event.message);
                }
            }, 
            {escape: true}
        );
    }
</script>

<p>Open the browser console and then click the buttons.</p>

<button onclick="myApp.fetchItem();">Fetch Item</button>
<button onclick="myApp.storeItem();">Store Item</button>

<button onclick="myApp.fetchContact();">Fetch Contact</button>
<button onclick="myApp.storeContact();">Store Contact</button>

Controller:

 public with sharing class DateTimeSerializationController {

 public class MyItem {
    public String name      { get; set; }
    public String theDateS   { get;  set; }     // Add new property of String type
    public DateTime theDate   { get; set; }
}

@RemoteAction
public static MyItem fetchTheItem() {
    MyItem item = new MyItem();
    item.name = 'Item Name';
    item.theDate = DateTime.now();
    item.theDateS = '';
    return item;
}

@RemoteAction
public static MyItem storeTheItem(MyItem item) {
    // DML or something similar could happen here   
    Long longtime = Long.valueOf(item.theDateS);   // Convert Sting to Long
    DateTime dt = DateTime.newInstance(longtime);  // Convert long to DateTime   
    item.thedate = dt;                            // Set the DateTime variable

    system.debug(item);

    return item;
}

@RemoteAction
public static Contact fetchTheContact() {
    Contact item = new Contact();
    item.FirstName = 'Joe';
    item.LastName = 'Bob';
    item.My_Date__c = DateTime.now();
    return item;
}

@RemoteAction
public static Contact storeTheContact(Contact item) {
    // DML or something similar could happen here
    insert item;
    system.debug(item);

    return item;
}
 }
  • 1
    Atul, my concern with this approach is that it requires the creation of additional members on the Apex class. If there were originally 20 DateTime members on the class, the creation of 20 additional string representation types for them to support serialization could create a bit of a mess. – Mark Pond Sep 4 '15 at 18:17
  • I agree. This is just workaround and not proper solution/ fix for this issue. – AtulRajguru9 Sep 7 '15 at 5:28
-1

One option is to have two flavor of setters for the field.


public class MyItem {
    public String name      { get; set; }
    public DateTime theDate   ; 
    public DateTime getTheDate() {return theDate;}
    public void setTheDate(DateTime theDate) {this.theDate = theDate;} 
    public void setTheDate(Long theDate) {this.theDate = DateTime.newInstance(theDate);} 
}

I prefer to send long from JS to backend as it will free us from timezone conversion. Timezone becomes very complex issue if the timezone on the local browser also come in to the play. Also the rendering datetime to string is browser dependent functionality but converting datetime to millisecond will be consistent across browsers.

  • have tried this in this context? when i tried, this does not work and gives same error. Visualforce Remoting Exception: Unable to convert date '1441257971170' to Apex type Datetime. – AtulRajguru9 Sep 3 '15 at 5:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.