And, to be fair, I did have some reason for thinking that Table Storage would support DateTimeOffset values. Below is an example of a very basic ITableEntity class that can be read from/written to Table Storage using the Azure SDK methods in the
Microsoft.WindowsAzure.Storage.Table namespace:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class ExampleEntity : ITableEntity { public void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext) { ExampleDateWithTimeZone = properties.GetDateTimeOffset("ExampleDateWithTimeZone").GetValueOrDefault(); } public IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext) { Dictionary<string, EntityProperty> properties = new Dictionary<string, EntityProperty>(); properties.Add("ExampleDateWithTimeZone", EntityProperty.GeneratePropertyForDateTimeOffset(ExampleDateWithTimeZone)); return properties; } public DateTimeOffset ExampleDateWithTimeZone { get; set;} public string ETag { get; set;} public string PartitionKey{ get; set;} public string RowKey{ get; set;} public DateTimeOffset Timestamp{ get; set;} } |
Look at lines 5 and 11: there are GetDateTimeOffset and GeneratePropertyForDateTimeOffset extension methods to read and write DateTimeOffset values, but no equivalent methods that read and write DateTime values. I saw this and thought “Great! It only supports dates with timezone information, which makes sense when the datacentre will normally be in a different timezone from the users”.
But unfortunately not…
When you do use those tempting helper methods for DateTimeOffSets what happens is:
- When you write the value, Azure converts it to GMT, so
24/04/2014 14:31 +02:00
becomes
24/04/2014 12:31 +00:00 - When you read it back you get the GMT value.
I didn’t notice this for a while because until the clocks changed in spring all my work on this project had taken place in GMT (one of the perils of developing in the U.K; you’re generally pretty careful about date formats, but not so careful about timezones). I did have some unit tests that used DateTimeOffset values with timezones but they were passing because according to DateTimeOffset.Equals():
24/04/2014 14:31 +02:00 == 24/04/2014 12:31 +00:00
(I can’t quite decide whether that’s a good thing or not)
So what did I do?
I still needed to store DateTimeOffset values in Azure Table Storage, so I converted them to strings. Not that revolutionary, but works quite nicely. In case I ever needed to sort them as DateTimeOffsets I used the format string “yyyyMMddHHmmssfffffffzzz”, so
01 May 2014 23:11:19 +01:00
becomes:
201405012312508966200+01:00
It turned out to be a fairly easy change to introduce because all my ReadEntity and WriteEntity implementations made heavy use of the same set of Extension Methods that look roughly like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public static void AddPropertyIfNotNull(this Dictionary<string, EntityProperty> properties, string propertyName, int? propertyValue) { if (propertyValue != null) { properties.Add(propertyName, EntityProperty.GeneratePropertyForInt(propertyValue)); } } public static int? GetInt32(this IDictionary<string, EntityProperty> properties, string propertyName) { return getValue(properties, propertyName, ep => ep.Int32Value, () => null); } private static T getValue<T>(IDictionary<string, EntityProperty> properties, string propertyName, Func<EntityProperty, T> valueAccessor, Func<T> nullValue) { if (properties.ContainsKey(propertyName)) { return valueAccessor(properties[propertyName]); } else { return nullValue(); } } |
I have AddPropertyIfNotNull and GetXXX methods for every primitive type that I need, including DateTimeOffset. So all I had to do was change the extension methods for DateTimeOffset to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private const string DATETIMEOFFSET_FORMAT = "yyyyMMddHHmmssfffffffzzz"; public static DateTimeOffset? GetDateTimeOffset(this IDictionary<string, EntityProperty> properties, string propertyName) { string valueAsString = getValue(properties, propertyName, ep => ep.StringValue, () => null); if (valueAsString == null) { return null; } else { return DateTimeOffset.ParseExact(valueAsString, DATETIMEOFFSET_FORMAT, CultureInfo.DefaultThreadCurrentUICulture); } } public static void AddPropertyIfNotNull(this Dictionary<string, EntityProperty> properties, string propertyName, DateTimeOffset? propertyValue) { if (propertyValue != null) { properties.Add(propertyName, EntityProperty.GeneratePropertyForString(propertyValue.Value.ToString(DATETIMEOFFSET_FORMAT))); } } |
And the rest of the application carried on working as normal.
And if Azure Table Storage starts supporting DateTimeOffset natively before I go live all I have to do is switch it back…
This is the same problem even we are also facing, somehow I feel that making this alternate way is not a good way as in DB if we need to perform some datetime activity we again need to parse it back.
ReplyDeleteI have read this post. collection of post is a nice one Azure Online Course Bangalore
ReplyDeleteTHIS IS AN VERY GOOD BLOG TO READ
ReplyDeleteAzure Training in Chennai | Certification | Azure Online Training Course | Azure Training in Bangalore | Certification | Azure Online Training Course | Azure Training in Hyderabad | Certification | Azure Online Training Course | Azure Training in Pune | Certification | Azure Online Training Course | Azure Training | microsoft azure certification | Azure Online Training Course