Binding to the new XAML DatePicker and TimePicker controls to the same DateTime value

The Windows 8.1 Preview release includes a number of new features for XAML applications.  Two of the new controls added are the DatePicker and TimePicker.  These controls make it easy to allow the user of your app to pick a date or a time.  

The downside of these controls is that the DatePicker.Date property is a DateTimeOffset (not a DateTime) and the TimePicker.Time property is a TimeSpan (not a DateTime).  There isn’t anything wrong with DateTimeOffset and TimeSpan other than that they just aren’t usually types I see in Models. 

Binding the DatePicker.Date property to a DateTime is accomplished easily using a simple converter class. It can event support two way data binding.  Binding the TimePicker.Time property to a DateTime is easy for one-way binding, but not so with two-way binding.

As you read through the post below, you may want to have my demo project.  You can grab it here: DateTimePickerDemo.zip

TwoWay DatePicker & DateTime Binding

So, I’m going to keep this simple.  I have a MainViewModel with a single DateTime property called “SomeDateTime” (nice name eh!).  Here’s the code for my ViewModel:

public class MainViewModel : ObservableObject
{

  //Initialize someDateTime with a default value
  private DateTime someDateTime = DateTime.Parse("07/21/1969 2:56AM");

  public DateTime SomeDateTime
  {
    get { return someDateTime; }
    set { Set(ref someDateTime,value); }
  }
   
}}

Then, I have some pretty simple markup for MainPage.xaml:

<Page ...>

  <Page.DataContext>
    <ViewModels:MainViewModel/>
  </Page.DataContext>

  <!-- ... -->

  <DatePicker Header="SomeDateTime Date" Margin="10" FontSize="24"/>
  <TimePicker Header="SomeDateTime Time" Margin="10" FontSize="24"/>
  <TextBlock Text="{Binding SomeDateTime}" Margin="10" FontSize="24" />

  <!-- ... -->

</Page>

In fact, if you look in the XAML snippet above you can see that the TextBlock.Text property is bound up to the SomeDateTime property on the MainViewModel. However, binding the DatePicker.Date property to SomeDateTime will require one more piece of code; an IValueConverter that converts DateTime values to/from DateTimeOffset values. 

The IValueConverter interface defines two methods, “Convert” and “ConvertBack” .  The “Convert” method is used to convert a source value (DateTime in our case) into the required target type (DateTimeOffset in our case).  The “ConvertBack” method can be used in TwoWay data binding to convert the value in the target property (a DateTimeOffset) back to the original data type (a DateTime in this case).

There are some issues around maintaining the timezone information as you convert between DateTime and DateTimeOffset, you can read more about it here: "Converting Between DateTime and DateTimeOffset". With that in mind, here is the source code for my DateTimeToDateTimeOffsetConverter IValueConverter implementation:

public class DateTimeToDateTimeOffsetConverter : IValueConverter
{

  public object Convert(object value, Type targetType, object parameter, string language)
  {
    try
    {
      DateTime date = (DateTime)value;
      return new DateTimeOffset(date);
    }
    catch (Exception ex)
    {
      return DateTimeOffset.MinValue;
    }
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    try
    {
      DateTimeOffset dto = (DateTimeOffset)value;
      return dto.DateTime;
    }
    catch (Exception ex)
    {
      return DateTime.MinValue;
    }
  }
}

With that created, I can add a resource to the page, and then modify the markup for the DatePicker to bind its Date property to the SomeDateTime property in the view model.

<Page.Resources>
  <Converters:DateTimeToDateTimeOffsetConverter 
    x:Key="DateTimeToDateTimeOffsetConverter"/>
</Page.Resources>

<!-- ... -->

<DatePicker 
  Header="SomeDateTime Date" 
  Margin="10" 
  FontSize="24"
  Date="{Binding SomeDateTime, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>

The solution above supports TwoWay data binding between the DatePicker.Date and a DateTime property.  Again, If your data is TimeZone sensitive, make sure you are aware of the intricacies of time zone management as you convert between them. 

OneWay TimePicker & DateTime Binding

First off, let me be clear, the issue I am taking about here is when your model (or view model) has a DateTime value, but you want to allow the user to edit the time component with a TimePicker.  If your Model / ViewModelhas a TimeSpan instead, then you are all set.  Just bind the TimePicker.Time up to it, and go to town. 

It’s when you are trying to manage the time component of a DateTime component that your job is a little more difficult. This is because the TimePicker.Time property is a TimeSpan.  The TimeSpan type has no concept of date it simply represents a period (span) of time.  You can convert a DateTime to a TimeSpan by subtracting the DateTime.Date out and leaving just the Time as a TimeSpan.  However, there is no logical way to convert from a TimeSpan back to the original date.  The date component is lost in the original conversion.  So, for that reason, you can only really do OneWay data binding with a TimePicker and a DateTime.

Here’s my DateTimeToTimeSpanConverter source:

public class DateTimeToTimeSpanConverter : IValueConverter
{

  public object Convert(object value, Type targetType, object parameter, string language)
  {
    try
    {
      DateTime dt = (DateTime)value;
      //Get the timespan from subtracting the date from the original DateTime
      //this returns a timespan representing the time component of the DateTime
      TimeSpan ts = dt - dt.Date;
      return ts;
    }
    catch (Exception ex)
    {
      return TimeSpan.MinValue;
    }
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    //It just doesn't make sense to convert back to a datetime. 
    //There is no concept representation of date in the incoming TimeSpan value.  
    throw new NotImplementedException();
  }
}

and modified XAML markup:

<Page.Resources>
  <Converters:DateTimeToTimeSpanConverter 
    x:Key="DateTimeToTimeSpanConverter"/>
  <!--...-->
</Page.Resources>

<!--...-->

<TimePicker 
  Header="SomeDateTime Time” 
  Margin="10" 
  FontSize="24" 
  Time="{Binding SomeDateTime, Converter={StaticResource DateTimeToTimeSpanConverter}}"/>

Notice that "parameter" argument in the Convert and ConvertBack methods? If we could pass the original DateTime along as the parameter, that would be a solution. The parameter value is supplied as a literal in via the ConverterParameter attribute in the Binding Markup Extension. And as I said, it can only take literals. It can’t be bound to another value. So we can’t dynamically pass the DateTime in that way. Bummer. There are some posts by others on how to create a binding ConverterParameter, but they are more work than they’re worth in my opion.

TwoWay TimePicker & DateTime Binding via a TimeSpan proxy

To truly support TwoWay databinding between a TimePicker and a DateTime, you have to introduce an intermediate TimeSpan property that acts as a proxy to the DateTime time component.  So, I guess the word “truly” isn’t true.  You bind to a TimeSpan and let it manage the DateTime value behind the scenes. 

In my sample project, to make this work, I added the following property to my MainViewModel:

public TimeSpan SomeDateTimeTimeSpanProxy
{
  get 
  {
    //Extract the timespan from the original datetime
    return someDateTime - someDateTime.Date; 
  }
  set 
  {
    //See if the timespan is different the the current value
    if (SomeDateTimeTimeSpanProxy != value)
    {
      //If it is, set the original date time to the
      //original date, plus the new timespan value
      SomeDateTime = someDateTime.Date.Add(value);
      //Raise the PropertyChanged event fro the timespan property
      NotifyPropertyChanged("SomeDateTimeTimeSpanProxy");
    }
  }
}

Then, I can remove the DateTimeToTimeSpanConverter ressource, and modify the <TimePicker> element’s data binding as follows:

<TimePicker 
  Header="SomeDateTime Time" 
  Margin="10" FontSize="24" 
  Time="{Binding SomeDateTimeTimeSpanProxy, Mode=TwoWay}"  />

Wrap Up

None of the above was difficult, but hopefully it made sense and gives you some insight into how you can use the new DatePicker and TimePicker controls with DateTime values. 

I did notice that when I changed a date using the DatePicker, the “AM/PM” aspect of the associated time get’s reset to PM each time.  I’m not sure if that is a bug, or an anomoly of the DateTimeOffset/DateTime conversion.  I’ll need to look into that more. 

Also, I discussed this topic with my teammate Jerry Nixon and he suggested a wrapper class that had the Original DateTime value and proxy properites not only for the Time, but Date component as well.  That is something you may also want to consider.  I was trying to do it with as little impact to the original Model / ViewModel as possible, and a new type could cause wrinkles through the layers. 

And, as I mentioned you can download my sample project here.  DateTimeDemoPicker.zip