In my application I retrieve domain objects via a web service. In the web service data, I know all the date values are UTC, but the web service does not format its xs:dateTime
values as UTC dates. (In other words the letter Z
is not appended to the end of each date to indicate UTC.)
I cannot change the way the web service behaves at this time, but as a workaround I have created a method which I call immediately after the objects from the web service have been deserialized.
private void ExplicitlyMarkDateTimesAsUtc<T>(T obj) where T : class
{
Type t = obj.GetType();
// Loop through the properties.
PropertyInfo[] props = t.GetProperties();
for (int i = 0; i < props.Length; i++)
{
PropertyInfo p = props[i];
// If a property is DateTime or DateTime?, set DateTimeKind to DateTimeKind.Utc.
if (p.PropertyType == typeof(DateTime))
{
DateTime date = (DateTime)p.GetValue(obj, null);
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
p.SetValue(obj, date, null);
}
// Same check for nullable DateTime.
else if (p.PropertyType == typeof(Nullable<DateTime>))
{
DateTime? date = (DateTime?)p.GetValue(obj, null);
DateTime? newDate = DateTime.SpecifyKind(date.Value, DateTimeKind.Utc);
p.SetValue(obj, newDate, null);
}
}
}
The method takes an object and loops through its properties, finding the properties that are either DateTime
or Nullable<DateTime>
, and then (is supposed to) explicitly sets the DateTime.Kind
property for each of the property values to DateTimeKind.Utc
.
The code does not throw any exceptions, but obj
never gets its DateTime properties changed. In the debugger p.SetValue(obj, date, null);
is called, but obj
never gets modified.
Why aren't changes getting applied to obj
?
Syntax: public static DateTime SpecifyKind (DateTime value, DateTimeKind kind); Parameters: value: It is the date and time. kind: It is one of the enumeration values which indicates whether the new object represents local time, UTC, or neither.
DateTimeKind. One of the enumeration values that indicates what the current time represents. The default is Unspecified.
DateTime contains other properties as well, like Hour, Minute, Second, Millisecond, Year, Month, and Day. The other properties of DateTime are: We can get the name of the day from the week with the help of the DayOfWeek property. To get the day of the year, we will use DayOfYear property.
Works fine when I try it. Beware that you are only changing the Kind, not the time. And you don't handle null dates properly, you cannot use date.Value if date.HasValue is false. Make sure that the exception isn't caught silently and bypassing the rest of the property assignments. Fix:
// Same check for nullable DateTime.
else if (p.PropertyType == typeof(Nullable<DateTime>)) {
DateTime? date = (DateTime?)p.GetValue(obj, null);
if (date.HasValue) {
DateTime? newDate = DateTime.SpecifyKind(date.Value, DateTimeKind.Utc);
p.SetValue(obj, newDate, null);
}
}
See http://derreckdean.wordpress.com/2013/04/24/converting-all-datetime-properties-of-an-object-graph-to-local-time-from-utc/ for blog post. I use this code to convert a WCF response object graph to have all local times:
/// <summary>
/// Since all dates in the DB are stored as UTC, this converts dates to the local time using the Windows time zone settings.
/// </summary>
public static class AllDateTimesAsUTC
{
/// <summary>
/// Specifies that an object's dates are coming in as UTC.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static T AllDatesAreUTC<T>(this T obj)
{
if (obj == null)
{
return default(T);
}
IterateDateTimeProperties(obj);
return obj;
}
private static void IterateDateTimeProperties(object obj)
{
if (obj == null)
{
return;
}
var properties = obj.GetType().GetProperties();
//Set all DaetTimeKinds to Utc
foreach (var prop in properties)
{
var t = prop.PropertyType;
if (t == typeof(DateTime) || t == typeof(DateTime?))
{
SpecifyUtcKind(prop, obj);
}
else if (t.IsEnumerable())
{
var vals = prop.GetValue(obj, null);
if (vals != null)
{
foreach (object o in (IEnumerable)vals)
{
IterateDateTimeProperties(o);
}
}
}
else
{
var val = prop.GetValue(obj, null);
if (val != null)
{
IterateDateTimeProperties(val);
}
}
}
//properties.ForEach(property => SpecifyUtcKind(property, obj));
return; // obj;
}
private static void SpecifyUtcKind(PropertyInfo property, object value)
{
//Get the datetime value
var datetime = property.GetValue(value, null);
DateTime output;
//set DateTimeKind to Utc
if (property.PropertyType == typeof(DateTime))
{
output = DateTime.SpecifyKind((DateTime)datetime, DateTimeKind.Utc);
}
else if (property.PropertyType == typeof(DateTime?))
{
var nullable = (DateTime?)datetime;
if (!nullable.HasValue) return;
output = (DateTime)DateTime.SpecifyKind(nullable.Value, DateTimeKind.Utc);
}
else
{
return;
}
Debug.WriteLine(" ***** Converted date from {0} to {1}.", datetime, output);
datetime = output.ToLocalTime();
//And set the Utc DateTime value
property.SetValue(value, datetime, null);
}
internal static Type[] ConvertibleTypes = {typeof(bool), typeof(byte), typeof(char),
typeof(DateTime), typeof(decimal), typeof(double), typeof(float), typeof(int),
typeof(long), typeof(sbyte), typeof(short), typeof(string), typeof(uint),
typeof(ulong), typeof(ushort)};
/// <summary>
/// Returns true if this Type matches any of a set of Types.
/// </summary>
/// <param name="types">The Types to compare this Type to.</param>
public static bool In(this Type type, params Type[] types)
{
foreach (Type t in types) if (t.IsAssignableFrom(type)) return true; return false;
}
/// <summary>
/// Returns true if this Type is one of the types accepted by Convert.ToString()
/// (other than object).
/// </summary>
public static bool IsConvertible(this Type t) { return t.In(ConvertibleTypes); }
/// <summary>
/// Gets whether this type is enumerable.
/// </summary>
public static bool IsEnumerable(this Type t)
{
return typeof(IEnumerable).IsAssignableFrom(t);
}
/// <summary>
/// Returns true if this property's getter is public, has no arguments, and has no
/// generic type parameters.
/// </summary>
public static bool SimpleGetter(this PropertyInfo info)
{
MethodInfo method = info.GetGetMethod(false);
return method != null && method.GetParameters().Length == 0 &&
method.GetGenericArguments().Length == 0;
}
}
(Some of the code came from other SO posts.)
To use: call .AllDatesAreUTC() from any object. It will walk the graph and do the local time conversions.
void svc_ZOut_GetZOutSummaryCompleted(object sender, ZOut_GetZOutSummaryCompletedEventArgs e)
{
svc.ZOut_GetZOutSummaryCompleted -= new EventHandler<ZOut_GetZOutSummaryCompletedEventArgs>(svc_ZOut_GetZOutSummaryCompleted);
svc = null;
var whenDone = (Action<bool, ZOutResult>)e.UserState;
if (e.Error != null)
{
FireOnExceptionRaised(e.Error);
whenDone(false, null);
}
else
{
var res = e.Result.AllDatesAreUTC();
FireOnSessionReceived(res.IsError, res.Session);
if (res.IsError == true)
{
whenDone(false, null);
}
else
{
whenDone(true, res.Result);
}
}
}
You can change the behavior to mark times as UTC without changing the time itself by modifying the SpecifyUtcKind method.
EDIT: I don't recommend using this on an object graph with circular references, as per the conversation in the comments.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With