- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
DateTime is retrieved with DateTimeKind.Unspecified even if saved with DateTimeKind.Utc
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?
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.
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.
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;
}
}
}
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;
}
}
}
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;
}
}
}