Skip to content
Permalink
Browse files
AVRO-3078: Add local-timestamp-millis and local-timestamp-micros logi…
…cal types to C# (#1628)

* Add local-timestamp-millis and micros

* Add more UTC based local-timestamp tests

* Fix whitespace

Co-authored-by: Zoltan Csizmadia <CsizmadiaZ@valassis.com>
  • Loading branch information
zcsizmadia and Zoltan Csizmadia committed May 3, 2022
1 parent 193d528 commit c2bd724f8af1f67ef540f6b8f80fab88663a92bf
Showing 5 changed files with 218 additions and 1 deletion.
@@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;

namespace Avro.Util
{
/// <summary>
/// The 'local-timestamp-micros' logical type.
/// </summary>
public class LocalTimestampMicrosecond : LogicalUnixEpochType<DateTime>
{
/// <summary>
/// The logical type name for LocalTimestampMicrosecond.
/// </summary>
public static readonly string LogicalTypeName = "local-timestamp-micros";

/// <summary>
/// Initializes a new LocalTimestampMicrosecond logical type.
/// </summary>
public LocalTimestampMicrosecond()
: base(LogicalTypeName)
{
}

/// <inheritdoc/>
public override void ValidateSchema(LogicalSchema schema)
{
if (Schema.Type.Long != schema.BaseSchema.Tag)
{
throw new AvroTypeException("'local-timestamp-micros' can only be used with an underlying long type");
}
}

/// <inheritdoc/>
public override object ConvertToBaseValue(object logicalValue, LogicalSchema schema)
{
DateTime date = ((DateTime)logicalValue).ToUniversalTime();
return (date - UnixEpochDateTime).Ticks / TicksPerMicrosecond;
}

/// <inheritdoc/>
public override object ConvertToLogicalValue(object baseValue, LogicalSchema schema)
{
return UnixEpochDateTime.AddTicks((long)baseValue * TicksPerMicrosecond).ToLocalTime();
}
}
}
@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;

namespace Avro.Util
{
/// <summary>
/// The 'local-timestamp-millis' logical type.
/// </summary>
public class LocalTimestampMillisecond : LogicalUnixEpochType<DateTime>
{
/// <summary>
/// The logical type name for LocalTimestampMillisecond.
/// </summary>
public static readonly string LogicalTypeName = "local-timestamp-millis";

/// <summary>
/// Initializes a new LocalTimestampMillisecond logical type.
/// </summary>
public LocalTimestampMillisecond()
: base(LogicalTypeName)
{
}

/// <inheritdoc/>
public override void ValidateSchema(LogicalSchema schema)
{
if (Schema.Type.Long != schema.BaseSchema.Tag)
{
throw new AvroTypeException("'local-timestamp-millis' can only be used with an underlying long type");
}
}

/// <inheritdoc/>
public override object ConvertToBaseValue(object logicalValue, LogicalSchema schema)
{
DateTime date = ((DateTime)logicalValue).ToUniversalTime();
return (long)(date - UnixEpochDateTime).TotalMilliseconds;
}

/// <inheritdoc/>
public override object ConvertToLogicalValue(object baseValue, LogicalSchema schema)
{
return UnixEpochDateTime.AddMilliseconds((long)baseValue).ToLocalTime();
}
}
}
@@ -39,6 +39,8 @@ private LogicalTypeFactory()
{
{ Decimal.LogicalTypeName, new Decimal() },
{ Date.LogicalTypeName, new Date() },
{ LocalTimestampMillisecond.LogicalTypeName, new LocalTimestampMillisecond() },
{ LocalTimestampMicrosecond.LogicalTypeName, new LocalTimestampMicrosecond() },
{ TimeMillisecond.LogicalTypeName, new TimeMillisecond() },
{ TimeMicrosecond.LogicalTypeName, new TimeMicrosecond() },
{ TimestampMillisecond.LogicalTypeName, new TimestampMillisecond() },
@@ -693,6 +693,10 @@ public void NotSupportedSchema(string schema, Type expectedException)
{ ""name"" : ""timestampmillis"", ""type"" : {""type"": ""long"", ""logicalType"": ""timestamp-millis""} },
{ ""name"" : ""nullibiletimestampmicros"", ""type"" : [""null"", {""type"": ""long"", ""logicalType"": ""timestamp-micros""}] },
{ ""name"" : ""timestampmicros"", ""type"" : {""type"": ""long"", ""logicalType"": ""timestamp-micros""} },
{ ""name"" : ""nulliblelocaltimestampmillis"", ""type"" : [""null"", {""type"": ""long"", ""logicalType"": ""local-timestamp-millis""}] },
{ ""name"" : ""localtimestampmillis"", ""type"" : {""type"": ""long"", ""logicalType"": ""local-timestamp-millis""} },
{ ""name"" : ""nullibilelocaltimestampmicros"", ""type"" : [""null"", {""type"": ""long"", ""logicalType"": ""local-timestamp-micros""}] },
{ ""name"" : ""locallocaltimestampmicros"", ""type"" : {""type"": ""long"", ""logicalType"": ""local-timestamp-micros""} },
{ ""name"" : ""nullibiletimemicros"", ""type"" : [""null"", {""type"": ""long"", ""logicalType"": ""time-micros""}] },
{ ""name"" : ""timemicros"", ""type"" : {""type"": ""long"", ""logicalType"": ""time-micros""} },
{ ""name"" : ""nullibiletimemillis"", ""type"" : [""null"", {""type"": ""int"", ""logicalType"": ""time-millis""}] },
@@ -703,7 +707,7 @@ public void NotSupportedSchema(string schema, Type expectedException)
{ ""name"" : ""decimalfixed"", ""type"" : {""type"": {""type"" : ""fixed"", ""size"": 16, ""name"": ""df""}, ""logicalType"": ""decimal"", ""precision"": 4, ""scale"": 2} }
]
}",
new object[] { "schematest.LogicalTypes", typeof(Guid?), typeof(Guid), typeof(DateTime?), typeof(DateTime), typeof(DateTime?), typeof(DateTime), typeof(TimeSpan?), typeof(TimeSpan), typeof(TimeSpan?), typeof(TimeSpan), typeof(AvroDecimal?), typeof(AvroDecimal), typeof(AvroDecimal?), typeof(AvroDecimal) })]
new object[] { "schematest.LogicalTypes", typeof(Guid?), typeof(Guid), typeof(DateTime?), typeof(DateTime), typeof(DateTime?), typeof(DateTime), typeof(DateTime?), typeof(DateTime), typeof(DateTime?), typeof(DateTime), typeof(TimeSpan?), typeof(TimeSpan), typeof(TimeSpan?), typeof(TimeSpan), typeof(AvroDecimal?), typeof(AvroDecimal), typeof(AvroDecimal?), typeof(AvroDecimal) })]
[TestCase(@"
{
""namespace"": ""enum.base"",
@@ -199,6 +199,7 @@ public void TestTimestampMillisecond(string s, string e)
var avroTimestampMilli = new TimestampMillisecond();
var convertedDate = (DateTime)avroTimestampMilli.ConvertToLogicalValue(avroTimestampMilli.ConvertToBaseValue(date, schema), schema);
Assert.AreEqual(expectedDate, convertedDate);
Assert.AreEqual(DateTimeKind.Utc, convertedDate.Kind);
}

[TestCase("01/01/2019 14:20:00Z", "01/01/2019 14:20:00Z")]
@@ -232,6 +233,91 @@ public void TestTimestampMicrosecond(string s, string e)
var avroTimestampMicro = new TimestampMicrosecond();
var convertedDate = (DateTime)avroTimestampMicro.ConvertToLogicalValue(avroTimestampMicro.ConvertToBaseValue(date, schema), schema);
Assert.AreEqual(expectedDate, convertedDate);
Assert.AreEqual(DateTimeKind.Utc, convertedDate.Kind);
}

[TestCase("01/01/2019 14:20:00", "01/01/2019 14:20:00")]
[TestCase("05/05/2019 14:20:00", "05/05/2019 14:20:00")]
[TestCase("05/05/2019 00:00:00", "05/05/2019 00:00:00")]
[TestCase("01/01/2019 14:20:00.1", "01/01/2019 14:20:00.1")]
[TestCase("01/01/2019 14:20:00.01", "01/01/2019 14:20:00.01")]
[TestCase("01/01/2019 14:20:00.001", "01/01/2019 14:20:00.001")]
[TestCase("01/01/2019 14:20:00.0001", "01/01/2019 14:20:00")]
[TestCase("01/01/2019 14:20:00.0009", "01/01/2019 14:20:00")] // there is no rounding up
[TestCase("01/01/2019 14:20:00.0019", "01/01/2019 14:20:00.001")] // there is no rounding up
[TestCase("01/01/2019 14:20:00Z", "01/01/2019 14:20:00Z")] // UTC timestamps, but will check will in local TZ
[TestCase("01/01/2019 14:20:00.1Z", "01/01/2019 14:20:00.1Z")]
[TestCase("01/01/2019 14:20:00.01Z", "01/01/2019 14:20:00.01Z")]
[TestCase("01/01/2019 14:20:00.001Z", "01/01/2019 14:20:00.001Z")]
public void TestLocalTimestampMillisecond(string s, string e)
{
var schema = (LogicalSchema)Schema.Parse("{\"type\": \"long\", \"logicalType\": \"local-timestamp-millis\"}");

var date = DateTime.Parse(s, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.RoundtripKind);

if (date.Kind != DateTimeKind.Utc)
{
date = DateTime.Parse(s, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.AssumeLocal);
}

var expectedDate = DateTime.Parse(e, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.RoundtripKind);

if (expectedDate.Kind != DateTimeKind.Utc)
{
expectedDate = DateTime.Parse(e, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.AssumeLocal);
}

expectedDate = expectedDate.ToLocalTime();

var avroLocalTimestampMilli = new LocalTimestampMillisecond();
var convertedDate = (DateTime)avroLocalTimestampMilli.ConvertToLogicalValue(avroLocalTimestampMilli.ConvertToBaseValue(date, schema), schema);
Assert.AreEqual(expectedDate, convertedDate);
Assert.AreEqual(DateTimeKind.Local, convertedDate.Kind);
}

[TestCase("01/01/2019 14:20:00", "01/01/2019 14:20:00")]
[TestCase("05/05/2019 14:20:00", "05/05/2019 14:20:00")]
[TestCase("05/05/2019 00:00:00", "05/05/2019 00:00:00")]
[TestCase("01/01/2019 14:20:00.1", "01/01/2019 14:20:00.1")]
[TestCase("01/01/2019 14:20:00.01", "01/01/2019 14:20:00.01")]
[TestCase("01/01/2019 14:20:00.001", "01/01/2019 14:20:00.001")]
[TestCase("01/01/2019 14:20:00.0001", "01/01/2019 14:20:00.0001")]
[TestCase("01/01/2019 14:20:00.00001", "01/01/2019 14:20:00.00001")]
[TestCase("01/01/2019 14:20:00.000001", "01/01/2019 14:20:00.000001")]
[TestCase("01/01/2019 14:20:00.0000001", "01/01/2019 14:20:00")]
[TestCase("01/01/2019 14:20:00.0000009", "01/01/2019 14:20:00")] // there is no rounding up
[TestCase("01/01/2019 14:20:00.0000019", "01/01/2019 14:20:00.000001")] // there is no rounding up
[TestCase("01/01/2019 14:20:00Z", "01/01/2019 14:20:00Z")] // UTC timestamps, but will check will in local TZ
[TestCase("01/01/2019 14:20:00.1Z", "01/01/2019 14:20:00.1Z")]
[TestCase("01/01/2019 14:20:00.01Z", "01/01/2019 14:20:00.01Z")]
[TestCase("01/01/2019 14:20:00.001Z", "01/01/2019 14:20:00.001Z")]
[TestCase("01/01/2019 14:20:00.0001Z", "01/01/2019 14:20:00.0001Z")]
[TestCase("01/01/2019 14:20:00.00001Z", "01/01/2019 14:20:00.00001Z")]
[TestCase("01/01/2019 14:20:00.000001Z", "01/01/2019 14:20:00.000001Z")]
public void TestLocalTimestampMicrosecond(string s, string e)
{
var schema = (LogicalSchema)Schema.Parse("{\"type\": \"long\", \"logicalType\": \"local-timestamp-micros\"}");

var date = DateTime.Parse(s, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.RoundtripKind);

if (date.Kind != DateTimeKind.Utc)
{
date = DateTime.Parse(s, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.AssumeLocal);
}

var expectedDate = DateTime.Parse(e, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.RoundtripKind);

if (expectedDate.Kind != DateTimeKind.Utc)
{
expectedDate = DateTime.Parse(e, CultureInfo.GetCultureInfo("en-US").DateTimeFormat, DateTimeStyles.AssumeLocal);
}

expectedDate = expectedDate.ToLocalTime();

var avroLocalTimestampMicro = new LocalTimestampMicrosecond();
var convertedDate = (DateTime)avroLocalTimestampMicro.ConvertToLogicalValue(avroLocalTimestampMicro.ConvertToBaseValue(date, schema), schema);
Assert.AreEqual(expectedDate, convertedDate);
Assert.AreEqual(DateTimeKind.Local, convertedDate.Kind);
}

[TestCase("01:20:10", "01:20:10", false)]

0 comments on commit c2bd724

Please sign in to comment.