Saving Time Zones in Couchbase Lite .Net Using DateTimeOffset

.net

#1

I am using Couchbase Lite .Net in Xamarin and am trying to save dates and times while retaining the timezone information. To do this, I would like to use DateTimeOffset and not DateTime. This is because DateTime can only handle Local timezone or Utc, it has no concept of other timezones. In my testing, Couchbase seems to have no problem saving a DateTimeOffset property and the string in the Json that it saves has the correct string formatting, including timezone. For example:

2016-01-01T12:00:00-09:00

So saves appear to be working great. The issue is that when reading this property back in, CouchbaseLite will convert this property into a DateTime object. When it does this, the timezone gets changed to the local timezone and the time of day is adjusted accordingly. Although the tick count is the same as what was saved, I have now lost the timezone information.

To better illustrate the issue, I wrote some tests.

[Test]
public void CblSerializer_DateTimeOffset_RetainsTz(){
	var mgr = Manager.SharedInstance;
	using (var db = mgr.GetDatabase ("testdb")) {
		
		Document doc = db.CreateDocument ();
		string docid = doc.Id;
		var expectedTime = new DateTimeOffset (2016, 01, 01, 12, 0, 0, new TimeSpan (-9, 0, 0));
		var props = new Dictionary<string, object> {
			{ "text", "This is text" },
			{ "time", expectedTime }
		};
		doc.PutProperties (props);

		Document resultDoc = db.GetDocument (docid);
		
		//Test using Properties
		object resultObj;
		resultDoc.Properties.TryGetValue ("time", out resultObj);
		Type propertyType = resultObj.GetType ();  //The Type of the returned value is DateTime, not DateTimeOffset :(
		Assert.That ((DateTimeOffset)resultObj, Is.EqualTo (expectedTime));
		Assert.That (((DateTimeOffset)resultObj).Offset, Is.EqualTo (expectedTime.Offset));

		//Test using GetProperty
		var resultTime = resultDoc.GetProperty<DateTimeOffset> ("time");  //This fails and return a DateTimeOffset with zero ticks
		Assert.That (resultTime, Is.EqualTo (expectedTime));
		Assert.That (resultTime.Offset, Is.EqualTo (expectedTime.Offset));
	}
}

In the test above, resultObj is a DateTime object with Kind = “Local” (which for me EST). This causes the timezone to get changed to -5:00 instead of -9:00 when it is cast to DateTimeOffset so my time ends up being

{1/1/2016 4:00:00 PM -05:00}.

The issue, I think, is that Couchbase Lite seems to handle all time formatted strings as DateTime and is deserializing them as such before I can get access to the property. Is there way I can change that behavior? Like substitute my own JsonSerializer for couchbase to use or directly access the raw json?

I know I could hack this by storing the DateTimeOffset as a string and add some helper functions in my data model to handle parsing it. But the example I am giving here is much simpler than the problem I am having in my real project so I cannot do that unfortunately. (I am using data models in a library which uses DateTimeOffset).


Can I Install Custom JSON Converter for Couchbase Lite .Net?
#2

I had a look into this issue, and it seems that JSON .NET makes you choose between DateTime and DateTimeOffset overall at the serialization level (it makes sense since there is no class information stored with it by default). It seems like a simple switch to make everything be DateTimeOffset instead (which, from what I read, is generally better than simple DateTime) but that also will cause some problems (existing users may be relying on DateTime). However I have a few suggestions:

On my end I can provide a setting to use DateTimeOffset instead of DateTime so that the user can proactively choose to switch between the two (I’ve already made your unit test pass by adding a couple settings to the JsonSerializer settings). I’ve filed this issue to track it.

On your end, if you cannot wait, my suggestion would be to store the time zone information separately in your data model. There is not currently a way to switch out Json serialization implementations, or get access to the raw JSON unfortunately (the former is somewhat on ice at the moment and the latter is planned for some unspecified point in the future).


#3

Thanks for looking into this so quickly! Would it be possible for you to please support all of the modes of Json.Net DateParseHandling?
http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_DateParseHandling.htm

In addition to DateTime and DateTimeOffset, it also has a “None” mode where times are handled as Strings and have no special handling. In my case, in addition to wanting to store some of my own data with DateTimeOffset, I am storing other data using a 3rd party library that defines it’s own time class, and has it’s own serializers which uses DateParseHandling.None so it can manually handle the strings.


#4

I feel like I should warn you that this setting will be process wide, and not specific to any object, so any attempt to switch between the modes on a per object basis will be subject to concurrency problems. As long as your third party library doesn’t use ISO 8601 as its save format, however, you will be fine with the existing modes. JSON.NET will only parse into instances of DateTime* if the format string matches the expected one. Does this sound like something that is possible (and reasonable) to do?

Making the JSON serialization per object requires some architectural changes that I am not ready to make at the moment.


#5

I understand the challenge with the process wide setting. I’m hoping the “None” mode makes it so that Couchbase would not try to do anything intelligent with parsing and just return a string. Then I could parse the string accordingly depending on Doc type.

For scenarios like this where CBL’s default parsing behavior causes issues and the user wants to manually parse it, have you considered providing an attribute that could be set that tells CBL not to try to do anything intelligent when parsing a particular property? Or somehow providing read access to the raw json for the whole document?


#6

This has been talked about before as a 2.0 feature.

I don’t think this would work. This parsing behavior is governed by JSON .NET and it is set on the serializer object. There is also an item converter type but if I expose that then I might as well commit to never being able to make the JSON parsing engine pluggable ^^;. I think I can put in the “None” though, with a warning there that even if you put in a DateTime object, that’s not what you will be getting out.


#7

I think supporting None is a good solution for now. It would at least be clear to anyone who has used json.net. I look forward to 2.0 though!


#8

1.3 is coming first, so 2.0 is still a ways off (we need to plan carefully and give enough time to deprecate APIs so that we can clean up the surface area for 2.0 when we are finally allowed some breaking changes).

I added in the option to ignore the date time auto-deserialization of DateTime objects in commit 27421812