138

Asp.net-MVC now allows for implicit binding of DateTime objects. I have an action along the lines of

public ActionResult DoSomething(DateTime startDate) 
{ 
... 
}

This successfully converts a string from an ajax call into a DateTime. However, we use the date format dd/MM/yyyy; MVC is converting to MM/dd/yyyy. For example, submitting a call to the action with a string '09/02/2009' results in a DateTime of '02/09/2009 00:00:00', or September 2nd in our local settings.

I don't want to roll my own model binder for the sake of a date format. But it seems needless to have to change the action to accept a string and then use DateTime.Parse if MVC is capable of doing this for me.

Is there any way to alter the date format used in the default model binder for DateTime? Shouldn't the default model binder use your localisation settings anyway?

Sam Wessel
  • 8,830
  • 8
  • 40
  • 44
  • Hey.. Just change your system date format- DD/MM/yyyy to MM/DD/yyyy and done it.. I also have same problem , i solved it by changing system date format. – banny Apr 27 '15 at 06:20
  • @banny if application is global and might be every one has not same date time format , how could you do this ? , you not suppose to go and change every ones date time Format.. – Ravi Mehta Feb 02 '16 at 11:43
  • None of these answers are helping me. The form needs to be localized. Some users may have the date one way, others the other way. Setting up something in the web.config. or global.asax isn't going to help. I'll keep searching for a better answer, but one way would just be to deal with the date as a string until I get back to c#. – astrosteve Mar 25 '16 at 15:11

10 Answers10

166

I've just found the answer to this with some more exhaustive googling:

Melvyn Harbour has a thorough explanation of why MVC works with dates the way it does, and how you can override this if necessary:

http://weblogs.asp.net/melvynharbour/archive/2008/11/21/mvc-modelbinder-and-localization.aspx

When looking for the value to parse, the framework looks in a specific order namely:

  1. RouteData (not shown above)
  2. URI query string
  3. Request form

Only the last of these will be culture aware however. There is a very good reason for this, from a localization perspective. Imagine that I have written a web application showing airline flight information that I publish online. I look up flights on a certain date by clicking on a link for that day (perhaps something like http://www.melsflighttimes.com/Flights/2008-11-21), and then want to email that link to my colleague in the US. The only way that we could guarantee that we will both be looking at the same page of data is if the InvariantCulture is used. By contrast, if I'm using a form to book my flight, everything is happening in a tight cycle. The data can respect the CurrentCulture when it is written to the form, and so needs to respect it when coming back from the form.

Community
  • 1
  • 1
Sam Wessel
  • 8,830
  • 8
  • 40
  • 44
  • Will do. That functionality is disabled for 48 hours after posting the question. – Sam Wessel Feb 10 '09 at 09:57
  • 48
    I will strongly disagree that technically this is correct. Model binder should ALWAYS behave the same with POST and GET. If invariant culture is the way to go for GET make it for POST too. Changing the behaviour depending on http verb is non-sense. – Bart Calixto Apr 02 '13 at 14:31
  • i have a question, our website hosted in another country, it needs `MM/dd/yyyy` format else it shows validation error `The field BeginDate must be a date.`, how can i make sever to accept `dd/MM/yyyy` format? – Shaiju T Apr 02 '15 at 06:03
  • The URL parameter should be unambigous, like using ISO standard formatting. Then culture settings would not matter. – nforss Apr 08 '15 at 07:32
  • this gives link explaining the cause but does not actually provide any easiest solution to this issue (such as by posting an ISO datetime string from the script), that way always works and we don't have to care about setting a specific culture on the server or ensure that the datetime format is identical between server and client. – Hopeless May 20 '20 at 09:11
38

I would globally set your cultures. ModelBinder pick that up!

  <system.web>
    <globalization uiCulture="en-AU" culture="en-AU" />

Or you just change this for this page.
But globally in web.config I think is better

Peter Gfader
  • 7,673
  • 8
  • 55
  • 56
  • 28
    Didn't for me. The date still comes through as null if I pass 23/10/2010. – GONeale Nov 24 '10 at 02:48
  • I think it easiest solution. For me it change format in all Date.ToString(). I think it will work with binding too, but did not check, sorry :-( – Sergey Makridenkov Sep 12 '12 at 13:16
  • 1
    I have my dev servers dateformat set to dd/MM/yyyy The modelbinder used the MM/dd/yyyy format. Setting the format in web.config to dd/MM/yyyy now forces the modelbinder to use the european format. In my opinion it should use the server's date settings though. Anyway you solved my problem. – Karel Nov 22 '12 at 14:56
  • That worked perfectly for me... somehow it felt strange though since I know that my application server is in the UK running a UK OS... :/ – Tallmaris Apr 24 '13 at 14:44
  • Worked perfectly in MVC4 running on Azure Websites – Matty Bear Aug 22 '13 at 10:19
33

I've been having the same issue with short date format binding to DateTime model properties. After looking at many different examples (not only concerning DateTime) I put together the follwing:

using System;
using System.Globalization;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    public class CustomDateBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext", "controllerContext is null.");
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext", "bindingContext is null.");

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (value == null)
                throw new ArgumentNullException(bindingContext.ModelName);

            CultureInfo cultureInf = (CultureInfo)CultureInfo.CurrentCulture.Clone();
            cultureInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

            try
            {
                var date = value.ConvertTo(typeof(DateTime), cultureInf);

                return date;
            }
            catch (Exception ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
        }
    }

    public class NullableCustomDateBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null)
                throw new ArgumentNullException("controllerContext", "controllerContext is null.");
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext", "bindingContext is null.");

            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (value == null) return null;

            CultureInfo cultureInf = (CultureInfo)CultureInfo.CurrentCulture.Clone();
            cultureInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";

            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

            try
            {
                var date = value.ConvertTo(typeof(DateTime), cultureInf);

                return date;
            }
            catch (Exception ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
        }
    }
}

To keep with the way that routes etc are regiseterd in the Global ASAX file I also added a new sytatic class to the App_Start folder of my MVC4 project named CustomModelBinderConfig:

using System;
using System.Web.Mvc;

namespace YourNamespaceHere
{
    public static class CustomModelBindersConfig
    {
        public static void RegisterCustomModelBinders()
        {
            ModelBinders.Binders.Add(typeof(DateTime), new CustomModelBinders.CustomDateBinder());
            ModelBinders.Binders.Add(typeof(DateTime?), new CustomModelBinders.NullableCustomDateBinder());
        }
    }
}

I then just call the static RegisterCustomModelBinders from my Global ASASX Application_Start like this:

protected void Application_Start()
{
    /* bla blah bla the usual stuff and then */

    CustomModelBindersConfig.RegisterCustomModelBinders();
}

An important note here is that if you write a DateTime value to a hiddenfield like this:

@Html.HiddenFor(model => model.SomeDate) // a DateTime property
@Html.Hiddenfor(model => model) // a model that is of type DateTime

I did that and the actual value on the page was in the format "MM/dd/yyyy hh:mm:ss tt" instead of "dd/MM/yyyy hh:mm:ss tt" like I wanted. This caused my model validation to either fail or return the wrong date (obviously swapping the day and month values around).

After a lot of head scratching and failed attempts the solution was to set the culture info for every request by doing this in the Global.ASAX:

protected void Application_BeginRequest()
{
    CultureInfo cInf = new CultureInfo("en-ZA", false);  
    // NOTE: change the culture name en-ZA to whatever culture suits your needs

    cInf.DateTimeFormat.DateSeparator = "/";
    cInf.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
    cInf.DateTimeFormat.LongDatePattern = "dd/MM/yyyy hh:mm:ss tt";

    System.Threading.Thread.CurrentThread.CurrentCulture = cInf;
    System.Threading.Thread.CurrentThread.CurrentUICulture = cInf;
}

It won't work if you stick it in Application_Start or even Session_Start since that assigns it to the current thread for the session. As you well know, web applications are stateless so the thread that serviced your request previously is ot the same thread serviceing your current request hence your culture info has gone to the great GC in the digital sky.

Thanks go to: Ivan Zlatev - http://ivanz.com/2010/11/03/custom-model-binding-using-imodelbinder-in-asp-net-mvc-two-gotchas/

garik - https://stackoverflow.com/a/2468447/578208

Dmitry - https://stackoverflow.com/a/11903896/578208

Community
  • 1
  • 1
WernerVA
  • 1,051
  • 1
  • 10
  • 16
13

It going to be slightly different in MVC 3.

Suppose we have a controller and a view with Get method

public ActionResult DoSomething(DateTime dateTime)
{
    return View();
}

We should add ModelBinder

public class DateTimeBinder : IModelBinder
{
    #region IModelBinder Members
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        DateTime dateTime;
        if (DateTime.TryParse(controllerContext.HttpContext.Request.QueryString["dateTime"], CultureInfo.GetCultureInfo("en-GB"), DateTimeStyles.None, out dateTime))
            return dateTime;
        //else
        return new DateTime();//or another appropriate default ;
    }
    #endregion
}

and the command in Application_Start() of Global.asax

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
Dmitry
  • 251
  • 3
  • 8
  • This is a decent starting point, but it doesn't correctly implement `IModelBinder`, particularly regarding validation. Also, it only works if the name of the `DateTime` is *dateTime*. – Sam Sep 08 '14 at 23:23
  • 2
    Also, I've found that `DateTime?`s only work if you add another call to `ModelBinders.Binders.Add` with `typeof(DateTime?)`. – Sam Sep 08 '14 at 23:25
8

It is also worth noting that even without creating your own model binder multiple different formats may be parsable.

For instance in the US all the following strings are equivalent and automatically get bound to the same DateTime value:

/company/press/may%2001%202008

/company/press/2008-05-01

/company/press/05-01-2008

I'd strongly suggest using yyyy-mm-dd because its a lot more portable. You really dont want to deal with handling multiple localized formats. If someone books a flight on 1st May instead of 5th January you're going to have big issues!

NB: I'm not clear exaclty if yyyy-mm-dd is universally parsed in all cultures so maybe someone who knows can add a comment.

Community
  • 1
  • 1
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
6

I set the below config on my MVC4 and it works like a charm

<globalization uiCulture="auto" culture="auto" />
JeeShen Lee
  • 3,476
  • 5
  • 39
  • 59
  • 3
    This worked for me as well. Note that the globalization element goes under Configuration > System.Web. http://msdn.microsoft.com/en-us/library/hy4kkhe0%28v=vs.85%29.aspx – Jowen Jan 14 '15 at 09:57
6

Try to use toISOString(). It returns string in ISO8601 format.

GET method

javascript

$.get('/example/doGet?date=' + new Date().toISOString(), function (result) {
    console.log(result);
});

c#

[HttpGet]
public JsonResult DoGet(DateTime date)
{
    return Json(date.ToString(), JsonRequestBehavior.AllowGet);
}

POST method

javascript

$.post('/example/do', { date: date.toISOString() }, function (result) {
    console.log(result);
});

c#

[HttpPost]
public JsonResult Do(DateTime date)
{
     return Json(date.ToString());
}
rnofenko
  • 9,198
  • 2
  • 46
  • 56
1
  public class DateTimeFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.RequestType == "GET")
        {

            foreach (var parameter in filterContext.ActionParameters)
            {
                var properties = parameter.Value.GetType().GetProperties();

                foreach (var property in properties)
                {
                    Type type = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;

                    if (property.PropertyType == typeof(System.DateTime) || property.PropertyType == typeof(DateTime?))
                    {
                        DateTime dateTime;

                        if (DateTime.TryParse(filterContext.HttpContext.Request.QueryString[property.Name], CultureInfo.CurrentUICulture, DateTimeStyles.None, out dateTime))
                            property.SetValue(parameter.Value, dateTime,null);
                    }
                }

            }
        }
    }
}
tobias
  • 1,502
  • 3
  • 22
  • 47
1
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var str = controllerContext.HttpContext.Request.QueryString[bindingContext.ModelName];
    if (string.IsNullOrEmpty(str)) return null;
    var date = DateTime.ParseExact(str, "dd.MM.yyyy", null);
    return date;
}
Sam
  • 40,644
  • 36
  • 176
  • 219
Teth
  • 19
  • 1
  • 1
    You should make your answer more rich by adding some explanations. – Alexandre Lavoie May 31 '13 at 11:35
  • From memory, this isn't consistent with the way the built-in model binders work, so it might not have the same secondary behaviour such as retaining the typed value for validation. – Sam Sep 08 '14 at 04:07
1

I set CurrentCulture and CurrentUICulture my custom base controller

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB");
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
    }
Korayem
  • 12,108
  • 5
  • 69
  • 56