Skip to content
Permalink
Browse files
AVRO-3475: Enforce time-millis and time-micros specification (#1626)
* Enforce  time-millis and time-micros specification

* Remove unused var

* Use TimeSpan formatting in exception message

* Use TimeSpan formatting in exception message

* Move TicksPerMicrosecond to LogicalUnixEpochType

* Remove debug line

Co-authored-by: Zoltan Csizmadia <CsizmadiaZ@valassis.com>
  • Loading branch information
zcsizmadia and Zoltan Csizmadia committed May 3, 2022
1 parent 923ea8e commit 193d528d391a27079ae5ea4b0a01f4819405f6ff
Showing 5 changed files with 51 additions and 14 deletions.
@@ -31,6 +31,11 @@ public abstract class LogicalUnixEpochType<T> : LogicalType
/// </summary>
protected static readonly DateTime UnixEpochDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

/// <summary>
/// Number of ticks per microsecond.
/// </summary>
protected const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;

/// <summary>
/// Initializes the base logical type.
/// </summary>
@@ -26,8 +26,7 @@ namespace Avro.Util
public class TimeMicrosecond : LogicalUnixEpochType<TimeSpan>
{
private static readonly TimeSpan _exclusiveUpperBound = TimeSpan.FromDays(1);
private const long _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;


/// <summary>
/// The logical type name for TimeMicrosecond.
/// </summary>
@@ -51,16 +50,29 @@ public override object ConvertToBaseValue(object logicalValue, LogicalSchema sch
{
var time = (TimeSpan)logicalValue;

if (time >= _exclusiveUpperBound)
throw new ArgumentOutOfRangeException(nameof(logicalValue), "A 'time-micros' value can only have the range '00:00:00' to '23:59:59'.");
ThrowIfOutOfRange(time, nameof(logicalValue));

return (time - UnixEpochDateTime.TimeOfDay).Ticks / _ticksPerMicrosecond;
// Note: UnixEpochDateTime.TimeOfDay is '00:00:00'. This could be 'return time.Ticks / TicksPerMicrosecond';
return (time - UnixEpochDateTime.TimeOfDay).Ticks / TicksPerMicrosecond;
}

/// <inheritdoc/>
public override object ConvertToLogicalValue(object baseValue, LogicalSchema schema)
{
return UnixEpochDateTime.TimeOfDay.Add(TimeSpan.FromTicks((long)baseValue * _ticksPerMicrosecond));
var time = TimeSpan.FromTicks((long)baseValue * TicksPerMicrosecond);

ThrowIfOutOfRange(time, nameof(baseValue));

// Note: UnixEpochDateTime.TimeOfDay is '00:00:00', so the Add is meaningless. This could be 'return time;'
return UnixEpochDateTime.TimeOfDay.Add(time);
}

private static void ThrowIfOutOfRange(TimeSpan time, string paramName)
{
if (time.Ticks < 0 || time >= _exclusiveUpperBound)
{
throw new ArgumentOutOfRangeException(paramName, $"A '{LogicalTypeName}' value must be at least '{TimeSpan.Zero}' and less than '{_exclusiveUpperBound}'.");
}
}
}
}
@@ -50,17 +50,29 @@ public override object ConvertToBaseValue(object logicalValue, LogicalSchema sch
{
var time = (TimeSpan)logicalValue;

if (time >= _exclusiveUpperBound)
throw new ArgumentOutOfRangeException(nameof(logicalValue), "A 'time-millis' value can only have the range '00:00:00' to '23:59:59'.");
ThrowIfOutOfRange(time, nameof(logicalValue));

// Note: UnixEpochDateTime.TimeOfDay is '00:00:00'. This could be 'return time.TotalMilliseconds;
return (int)(time - UnixEpochDateTime.TimeOfDay).TotalMilliseconds;
}

/// <inheritdoc/>
public override object ConvertToLogicalValue(object baseValue, LogicalSchema schema)
{
var noMs = (int)baseValue;
return UnixEpochDateTime.TimeOfDay.Add(TimeSpan.FromMilliseconds(noMs));
var time = TimeSpan.FromMilliseconds((int)baseValue);

ThrowIfOutOfRange(time, nameof(baseValue));

// Note: UnixEpochDateTime.TimeOfDay is '00:00:00'. This could be 'return time;'
return UnixEpochDateTime.TimeOfDay.Add(time);
}

private static void ThrowIfOutOfRange(TimeSpan time, string paramName)
{
if (time.Ticks < 0 || time >= _exclusiveUpperBound)
{
throw new ArgumentOutOfRangeException(paramName, $"A '{LogicalTypeName}' value must be at least '{TimeSpan.Zero}' and less than '{_exclusiveUpperBound}'.");
}
}
}
}
@@ -25,8 +25,6 @@ namespace Avro.Util
/// </summary>
public class TimestampMicrosecond : LogicalUnixEpochType<DateTime>
{
private const long _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;

/// <summary>
/// The logical type name for TimestampMicrosecond.
/// </summary>
@@ -49,13 +47,13 @@ public override void ValidateSchema(LogicalSchema schema)
public override object ConvertToBaseValue(object logicalValue, LogicalSchema schema)
{
var date = ((DateTime)logicalValue).ToUniversalTime();
return (date - UnixEpochDateTime).Ticks / _ticksPerMicrosecond;
return (date - UnixEpochDateTime).Ticks / TicksPerMicrosecond;
}

/// <inheritdoc/>
public override object ConvertToLogicalValue(object baseValue, LogicalSchema schema)
{
return UnixEpochDateTime.AddTicks((long)baseValue * _ticksPerMicrosecond);
return UnixEpochDateTime.AddTicks((long)baseValue * TicksPerMicrosecond);
}
}
}
@@ -246,6 +246,11 @@ public void TestTimestampMicrosecond(string s, string e)
[TestCase("01:20:10.0019", "01:20:10.001", false)] // there is no rounding up
[TestCase("23:59:59.999", "23:59:59.999", false)]
[TestCase("01:00:00:00", null, true)]
[TestCase("-00:00:00.001", null, true)]
[TestCase("-00:00:00.000001", null, true)]
[TestCase("-00:00:00.0000001", null, true)]
[TestCase("-00:01", null, true)]
[TestCase("-999999.00:00:00", null, true)]
public void TestTimeMillisecond(string s, string e, bool expectRangeError)
{
var timeMilliSchema = (LogicalSchema)Schema.Parse("{\"type\": \"int\", \"logicalType\": \"time-millis\"}");
@@ -284,6 +289,11 @@ public void TestTimeMillisecond(string s, string e, bool expectRangeError)
[TestCase("01:20:10.0000009", "01:20:10", false)]
[TestCase("23:59:59.999999", "23:59:59.999999", false)]
[TestCase("01:00:00:00", null, true)]
[TestCase("-00:00:00.001", null, true)]
[TestCase("-00:00:00.000001", null, true)]
[TestCase("-00:00:00.0000001", null, true)]
[TestCase("-00:01", null, true)]
[TestCase("-999999.00:00:00", null, true)]
public void TestTimeMicrosecond(string s, string e, bool expectRangeError)
{
var timeMicroSchema = (LogicalSchema)Schema.Parse("{\"type\": \"long\", \"logicalType\": \"time-micros\"}");

0 comments on commit 193d528

Please sign in to comment.