ASP.NET MVC datetime culture issue when passing value back to controller

This issue arises because you are using the GET method on your Form. The QueryString Value Provider in MVC always uses Invariant/US date format. See: MVC DateTime binding with incorrect date format

There are three solutions:

  1. Change your method to POST.
  2. As someone else says, change the date format to ISO 8601 "yyyy-mm-dd" before submission.
  3. Use a custom binder to always treat Query String dates as GB. If you do this you have to make sure that all dates are in that form:

    public class UKDateTimeModelBinder : IModelBinder
    {
    private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    
    /// <summary>
    /// Fixes date parsing issue when using GET method. Modified from the answer given here:
    /// https://stackoverflow.com/questions/528545/mvc-datetime-binding-with-incorrect-date-format
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="bindingContext">The binding context.</param>
    /// <returns>
    /// The converted bound value or null if the raw value is null or empty or cannot be parsed.
    /// </returns>
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var vpr = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    
        if (vpr == null)
        {
            return null;
    
        }
    
        var date = vpr.AttemptedValue;
    
        if (String.IsNullOrEmpty(date))
        {
            return null;
        }
    
        logger.DebugFormat("Parsing bound date '{0}' as UK format.", date);
    
        // Set the ModelState to the first attempted value before we have converted the date. This is to ensure that the ModelState has
        // a value. When we have converted it, we will override it with a full universal date.
        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, bindingContext.ValueProvider.GetValue(bindingContext.ModelName));
    
        try
        {
            var realDate = DateTime.Parse(date, System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB"));
    
            // Now set the ModelState value to a full value so that it can always be parsed using InvarianCulture, which is the
            // default for QueryStringValueProvider.
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, new ValueProviderResult(date, realDate.ToString("yyyy-MM-dd hh:mm:ss"), System.Globalization.CultureInfo.GetCultureInfoByIetfLanguageTag("en-GB")));
    
            return realDate;
        }
        catch (Exception)
        {
            logger.ErrorFormat("Error parsing bound date '{0}' as UK format.", date);
    
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, String.Format("\"{0}\" is invalid.", bindingContext.ModelName));
            return null;
        }
    }
    }
    

When submitting a date you should always try and submit it in the format "yyyy-MM-dd". This will allow for it to become culture independent.

I normally have a hidden field which maintains the date in this format. This is relatively simple using jQuery UI's datepicker.


You can create a Binder extension to handle the date in the culture format.

This is a sample I wrote to handle the same problem with Decimal type, hope you get the idea

 public class DecimalModelBinder : IModelBinder
 {
   public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
   {
     ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
     ModelState modelState = new ModelState { Value = valueResult };
     object actualValue = null;
     try
     {
       actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
     }
     catch (FormatException e)
     {
       modelState.Errors.Add(e);
     }

     bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
     return actualValue;
  }
}

Update

To use it simply declare the binder in Global.asax like this

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);

  //HERE you tell the framework how to handle decimal values
  ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());

  DependencyResolver.SetResolver(new ETAutofacDependencyResolver());
}

Then when the modelbinder has to do some work, it will know automatically what to do. For example, this is an action with a model containing some properties of type decimal. I simply do nothing

[HttpPost]
public ActionResult Edit(int id, MyViewModel viewModel)
{
  if (ModelState.IsValid)
  {
    try
    {
      var model = new MyDomainModelEntity();
      model.DecimalValue = viewModel.DecimalValue;
      repository.Save(model);
      return RedirectToAction("Index");
    }
    catch (RulesException ex)
    {
      ex.CopyTo(ModelState);
    }
    catch
    {
      ModelState.AddModelError("", "My generic error message");
    }
  }
  return View(model);
}

you can change the default model binder to use the user culture using IModelBinder

   public class DateTimeBinder : IModelBinder
   {
       public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
       {
           var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
           var date = value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);

           return date;    
       }
   }

And in the Global.Asax write:

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeBinder());

Read more at this excellent blog that describe why Mvc framework team implemented a default Culture to all users.