DateTime is retrieved with DateTimeKind.Unspecified even if saved with DateTimeKind.Utc

Posts   
 
    
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 09-Oct-2014 18:27:58   

I am on version 4.0 Final using adapter.

My issue is explained her: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=17566

The solution suggested by Otis can be implemented like this, I have not tested:

public class DateTimeAsUtcConverter : SystemTypeConverterBase<DateTime> {
    protected override object PerformConvertFrom(object value) {
        if (value is DateTime) {
            var dateTime = (DateTime) value;
            if (dateTime.Kind == DateTimeKind.Unspecified) {
                return new DateTime(dateTime.Ticks, DateTimeKind.Utc);
            }
            return value;
        }
        throw new NotSupportedException(CreateInvalidConvertFromExceptionMessage(value));
    }
}

This must be a common issue. Is there a smoother way to do this such as a global DateTimeKind project setting?

If not I need to introduce a type converter library just for this and apply that to all the datetime entity fields using the designer. I see that this can be done in the designer global settings.

If I need to do this, do you have any comments on the converter?

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 09-Oct-2014 20:32:18   

Please download the LLBLGen Pro Source code from the Customers' area. There you can find many examples for the full implementation of the TypeConverter.

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 09-Oct-2014 22:05:41   

Ok, thanks.

If there is no way to achieve this common task in LLBLGen other than by use of a TypeConverter, please consider my post as a feature request. I'd like to see a global project setting for it in the designer.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 10-Oct-2014 08:05:27   

The TypeConverter is the easiest way to solve this. The only difference from what you proposed is that it's not a built-in typeConverter.

Yes, i think it could be considered, but to be honest I don't think it will be built-in as it's not that common and it's a matter of how the data is saved and how the specified provider interpret that.

Using your own TypeConverter and auto-assign it to your datetime fields is not hard at all. Please give it a try.

David Elizondo | LLBLGen Support Team
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 10-Oct-2014 10:06:03   

Thanks.

It would be much easier for your customers if you would supply this type converter. I need to create a new library assembly for this class. All developers need to copy this to their llblgen installation. We also need to update the deploy package with a new assembly.

If it is not that common, I think it is because people don't realize this is a broken window. If code by mistake calls ToUniversalTime on a retrieved DateTime field saved in db as UTC, it will be converted as if it was in local time.

hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 10-Oct-2014 11:24:01   

It looks like it is not supported with a custom SystemTypeConverter, at least it is not showing in the designer.

using System;
using System.Collections;
using System.ComponentModel;
using SD.LLBLGen.Pro.ORMSupportClasses;

namespace Collabora.LLBLGen.TypeConverters {
    [Description("Converts local time to utc for saving to db. Sets DateTimeKind.Utc on datetime values retrieved from db.")]
    public class DateTimeAsUtcConverter : SystemTypeConverterBase<DateTime> {
        protected override object PerformConvertFrom(object value) {
            if (value is DateTime) {
                var dateTime = (DateTime)value;
                if (dateTime.Kind == DateTimeKind.Unspecified) {
                    return new DateTime(dateTime.Ticks, DateTimeKind.Utc);
                }
                return value;
            }
            throw new NotSupportedException(CreateInvalidConvertFromExceptionMessage(value));
        }

        protected override object PerformConvertTo(DateTime value, Type destinationType) {
            if (destinationType != typeof(DateTime)) {
                throw new NotSupportedException(CreateInvalidConvertToExceptionMessage(destinationType));
            }
            return value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value;
        }

        public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) {
            return DateTime.UtcNow;
        }
    }
}
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 10-Oct-2014 11:58:17   

The included DateTimeDateTimeUTCConverter (which is shipped with v4.0 and available in the runtime, so no external typeconverter needed) is that one sufficient?

That one is for a different kind of problem.

System type converters are type converters which are supported by the runtime. As yours is a custom type converter, you should create a custom type converter. This is a couple of lines of code, really.

See the below code as a guide:


using System;
using System.ComponentModel;
using System.Data;

namespace My.TypeConverters
{
    [Description("Converter with as core type System.DateTime and which converts DateTime values to/from UTC")]
    public class DateTimeToUtcConverter : TypeConverter
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DateTimeToUtcConverter"/> class.
        /// </summary>
        public DateTimeToUtcConverter()
        {
        }

        /// <summary>
        /// Returns whether this converter can convert an object of the given type to the type of this converter (DateTime).
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param>
        /// <returns>
        ///     <see langword="true "/>if this converter can perform the conversion; otherwise, <see langword="false"/>.
        /// </returns>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return (sourcetype==typeof(DateTime));
        }

        /// <summary>
        /// Returns whether this converter can convert the object to the specified type.
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="destinationType">A <see cref="T:System.Type"/> that represents the type you want to convert to.</param>
        /// <returns>
        ///     <see langword="true "/>if this converter can perform the conversion; otherwise, <see langword="false"/>.
        /// </returns>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return (sourcetype==typeof(DateTime));
        }

        /// <summary>
        /// Converts the given object to the type of this converter (DateTime).
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="culture">Ignored</param>
        /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the converted value, which is of type DateTime.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">The conversion could not be performed.</exception>
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            // convert to value which is sent to the DB
        }

        /// <summary>
        /// Converts the given value object to the specified type
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="culture">Ignored</param>
        /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
        /// <param name="destinationType">The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the converted value. 
        /// </returns>
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            // convert value read from DB to value which is used as in-memory entity field value
        }

        /// <summary>
        /// Creates an instance of the Type that this <see cref="T:System.ComponentModel.TypeConverter"/> is associated with (DateTime)
        /// </summary>
        /// <param name="context">ignored.</param>
        /// <param name="propertyValues">ignored.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> of type DateTime. 
        /// </returns>
        public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
        {
            return DateTime.Now;
        }
    }
}

Frans Bouma | Lead developer LLBLGen Pro
hotchill avatar
hotchill
User
Posts: 180
Joined: 22-Jan-2007
# Posted on: 10-Oct-2014 12:18:28   

Thanks Otis. I also found a good example on your web pages. I tested the below and it works for us.

For anyone else looking for a solution, this will do the trick. It also works for nullable DateTime.

using System;
using System.Collections;
using System.ComponentModel;
namespace Collabora.LLBLGen.TypeConverters {
    [Description("Converts local time to utc for saving to db. Sets DateTimeKind.Utc on datetime values retrieved from db.")]
    public class DateTimeAsUtcConverter : TypeConverter {

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
            return sourceType == typeof(DateTime);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
            return destinationType == typeof(DateTime);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) {
            if (value == null) {
                return null;
            }
            if (!(value is DateTime)) {
                throw new NotSupportedException("Conversion from a value of type '" + value.GetType() + "' to System.DateTime isn't supported");
            }
            var dateTime = (DateTime)value;
            return dateTime.Kind == DateTimeKind.Unspecified ? new DateTime(dateTime.Ticks, DateTimeKind.Utc) : value;
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) {
            if (value == null) {
                return null;
            }
            if (!(value is DateTime)) {
                throw new NotSupportedException("Conversion of a value of type '" + destinationType + "' isn't supported");
            }
            if (destinationType != typeof(DateTime)) {
                throw new NotSupportedException("Conversion to a value of type '" + destinationType + "' isn't supported");
            }
            var dt = (DateTime) value;
            return dt.Kind == DateTimeKind.Local ? dt.ToUniversalTime() : value;

        }

        public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) {
            return DateTime.UtcNow;
        }
    }
}