FixAntenna/NetCore/Message/HighPrecisionDateTimeParsers.cs (267 lines of code) (raw):
// Copyright (c) 2021 EPAM Systems
//
// Licensed 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
//
// http://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;
using Epam.FixAntenna.NetCore.Common;
using Epam.FixAntenna.NetCore.Helpers;
namespace Epam.FixAntenna.NetCore.Message
{
/// <summary>
/// Helper class for FIX date and time formats.
/// <para>
/// Provides ability for parsing different types of date/time values
/// from the buffer of bytes. It is also possible to work with high precision
/// values: micro- and nanoseconds.
///
/// </para>
/// </summary>
internal class HighPrecisionDateTimeParsers
{
/// <summary>
/// Parses the <c>TimeOnly</c> value from <c>buffer</c>.
/// The format for <c>TimeOnly</c> is HH:MM:SS[.sss][sss][sss]
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <returns> the local time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTimeOffset parseTimeOnly(byte[] buffer)
{
return parseTimeOnly(buffer, 0, buffer.Length);
}
/// <summary>
/// Parses the <c>TimeOnly</c> value from <c>buffer</c>.
/// The format for <c>TimeOnly</c> is HH:MM:SS[.sss][sss][sss]
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <param name="offset"> the initial offset </param>
/// <param name="count"> the length </param>
/// <returns> the local time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTime parseTimeOnly(byte[] buffer, int offset, int count)
{
if (buffer == null || buffer[offset + 2] != (byte)':' || buffer[offset + 5] != (byte)':')
{
throw new ArgumentException();
}
var hour = FixTypes.ParseNumberPart(buffer, offset, offset + 2);
if (hour < 0 || hour > 23)
{
throw new ArgumentException();
}
var minute = FixTypes.ParseNumberPart(buffer, offset + 3, offset + 5);
if (minute < 0 || minute > 59)
{
throw new ArgumentException();
}
var second = FixTypes.ParseNumberPart(buffer, offset + 6, offset + 8);
if (second < 0 || second > 60)
{
throw new ArgumentException();
}
var nanosecond = 0;
if (count >= 10)
{
if (buffer[offset + 8] != (byte)'.')
{
throw new ArgumentException();
}
nanosecond = ParseNano(buffer, offset, 9, count);
}
if (second == 60)
{
var time = new DateTimeBuilder().SetHour(hour).SetMinute(minute).SetSecond(59).SetNanosecond(nanosecond)
.Build(DateTimeKind.Utc);
return time.AddSeconds(1);
}
return new DateTimeBuilder().SetHour(hour).SetMinute(minute).SetSecond(second).SetNanosecond(nanosecond)
.Build(DateTimeKind.Utc);
}
/// <summary>
/// Parses the <c>TZTimeOnly</c> value from <c>buffer</c>.
/// The format for <c>TZTimeOnly</c> is HH:MM[:SS][.sss][sss][sss][Z | [ + | - hh[:mm]]]
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <returns> the offset time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTimeOffset parseTZTimeOnly(byte[] buffer)
{
return parseTZTimeOnly(buffer, 0, buffer.Length);
}
/// <summary>
/// Parses the <c>TZTimeOnly</c> value from <c>buffer</c>.
/// The format for <c>TZTimeOnly</c> is HH:MM[:SS][.sss][sss][sss][Z | [ + | - hh[:mm]]]
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <param name="offset"> the initial offset </param>
/// <param name="count"> the length </param>
/// <returns> the offset time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTimeOffset parseTZTimeOnly(byte[] buffer, int offset, int count)
{
var countWithoutTz = GetTimeCountWithoutTimeZone(buffer, offset, count);
var hour = FixTypes.ParseNumberPart(buffer, offset, offset + 2);
if (hour < 0 || hour > 23)
{
throw new ArgumentException();
}
var minute = FixTypes.ParseNumberPart(buffer, offset + 3, offset + 5);
if (minute < 0 || minute > 59)
{
throw new ArgumentException();
}
var second = 0;
var nanosecond = 0;
if (count > 5 && buffer[offset + 5] == (byte)':')
{
second = FixTypes.ParseNumberPart(buffer, offset + 6, offset + 8);
if (second < 0 || second > 60)
{
throw new ArgumentException();
}
if (count > 8 && buffer[offset + 8] == (byte)'.')
{
nanosecond = ParseNano(buffer, offset, 9, countWithoutTz);
}
}
var zoneOffset = DateTimeHelper.ParseZoneOffset(buffer, offset + countWithoutTz, count - countWithoutTz);
if (second == 60)
{
var time = new DateTimeBuilder().SetHour(hour).SetMinute(minute).SetSecond(59).SetNanosecond(nanosecond)
.Build(zoneOffset);
return time.AddSeconds(1);
}
return new DateTimeBuilder().SetHour(hour).SetMinute(minute).SetSecond(second).SetNanosecond(nanosecond)
.Build(zoneOffset);
}
/// <summary>
/// Parses the <c>Timestamp</c> value from <c>buffer</c> to <c>dateTime</c>.
/// The format for <c>Timestamp</c> is YYYYMMDD-HH:MM:SS[.sss][sss][sss].
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <returns> the local date time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTime ParseTimestamp(byte[] buffer)
{
return ParseTimestamp(buffer, 0, buffer.Length);
}
/// <summary>
/// Parses the <c>Timestamp</c> value from <c>buffer</c> to <c>dateTime</c>.
/// The format for <c>Timestamp</c> is YYYYMMDD-HH:MM:SS[.sss][sss][sss].
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <param name="offset"> the initial offset </param>
/// <param name="count"> the length </param>
/// <returns> the local date time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTime ParseTimestamp(byte[] buffer, int offset, int count)
{
if (buffer == null || buffer[offset + 8] != (byte)'-' || buffer[offset + 11] != (byte)':' ||
buffer[offset + 14] != (byte)':')
{
throw new ArgumentException("Invalid timestamp value: " +
StringHelper.NewString(buffer, offset, count));
}
var year = FixTypes.ParseYearPart(buffer, offset);
var month = FixTypes.ParseNumberPart(buffer, offset + 4, offset + 6);
if (month < 1 || month > 12)
{
throw new ArgumentException("Incorrect month value");
}
var date = FixTypes.ParseDatePart(buffer, offset, year, month);
if (buffer[offset + 8] != (byte)'-')
{
throw new ArgumentException("Incorrect date time delimiter");
}
var hour = FixTypes.ParseNumberPart(buffer, offset + 9, offset + 11);
if (hour < 0 || hour > 23)
{
throw new ArgumentException("Incorrect hour value");
}
var minute = FixTypes.ParseNumberPart(buffer, offset + 12, offset + 14);
if (minute < 0 || minute > 59)
{
throw new ArgumentException("Incorrect minute value");
}
var second = FixTypes.ParseNumberPart(buffer, offset + 15, offset + 17);
if (second < 0 || second > 60)
{
throw new ArgumentException("Incorrect second value");
}
var nanosecond = 0;
if (count >= 19)
{
if (buffer[offset + 17] != (byte)'.')
{
throw new ArgumentException();
}
nanosecond = ParseNano(buffer, offset, 18, count);
}
if (second == 60)
{
var dateTime = new DateTimeBuilder(year, month, date, hour, minute, 59).SetNanosecond(nanosecond)
.Build(DateTimeKind.Utc);
return dateTime.AddSeconds(1);
}
return new DateTimeBuilder(year, month, date, hour, minute, second).SetNanosecond(nanosecond)
.Build(DateTimeKind.Utc);
}
/// <summary>
/// Parses the <c>TZTimestamp</c> value from <c>buffer</c>.
/// The format for <c>TZTimestamp</c> is YYYYMMDD-HH:MM[:SS][.sss][sss][sss][Z | [ + | - hh[:mm]]]
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <returns> the offset time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTimeOffset ParseTzTimestamp(byte[] buffer)
{
return ParseTzTimestamp(buffer, 0, buffer.Length);
}
/// <summary>
/// Parses the <c>TZTimestamp</c> value from <c>buffer</c>.
/// The format for <c>TZTimestamp</c> is YYYYMMDD-HH:MM[:SS][.sss][sss][sss][Z | [ + | - hh[:mm]]]
/// </summary>
/// <param name="buffer"> the buffer of bytes </param>
/// <param name="offset"> the initial offset </param>
/// <param name="count"> the length </param>
/// <returns> the offset time structured from the given buffer
/// <p/>
/// In contrast to <c>Calendar</c> supports nanoseconds precision. </returns>
/// <exception cref="ArgumentException"> </exception>
public static DateTimeOffset ParseTzTimestamp(byte[] buffer, int offset, int count)
{
var year = FixTypes.ParseYearPart(buffer, offset);
var month = FixTypes.ParseNumberPart(buffer, offset + 4, offset + 6);
if (month < 1 || month > 12)
{
throw new ArgumentException("Incorrect month value");
}
var date = FixTypes.ParseDatePart(buffer, offset, year, month);
if (buffer[offset + 8] != (byte)'-')
{
throw new ArgumentException("Incorrect date time delimiter");
}
var countWithoutTz = 9 + GetTimeCountWithoutTimeZone(buffer, offset + 9, count - 9);
var hour = FixTypes.ParseNumberPart(buffer, offset + 9, offset + 11);
if (hour < 0 || hour > 23)
{
throw new ArgumentException("Incorrect hour value");
}
var minute = FixTypes.ParseNumberPart(buffer, offset + 12, offset + 14);
if (minute < 0 || minute > 59)
{
throw new ArgumentException("Incorrect minute value");
}
var second = 0;
var nanosecond = 0;
if (count > 14 && buffer[offset + 14] == (byte)':')
{
second = FixTypes.ParseNumberPart(buffer, offset + 15, offset + 17);
if (second < 0 || second > 60)
{
throw new ArgumentException("Incorrect second value");
}
if (count > 17 && buffer[offset + 17] == (byte)'.')
{
nanosecond = ParseNano(buffer, offset, 18, countWithoutTz);
}
}
var zoneOffset = DateTimeHelper.ParseZoneOffset(buffer, offset + countWithoutTz, count - countWithoutTz);
if (second == 60)
{
var time = new DateTimeBuilder(year, month, date, hour, minute, 59).SetNanosecond(nanosecond)
.Build(zoneOffset);
return time.AddSeconds(1);
}
return new DateTimeBuilder(year, month, date, hour, minute, second).SetNanosecond(nanosecond)
.Build(zoneOffset);
}
private static int GetTimeCountWithoutTimeZone(byte[] buffer, int offset, int count)
{
if (buffer[offset + count - 1] == (byte)'Z')
{
// zoneID = "Z"
return count - 1;
}
if (count > 6 && (buffer[offset + count - 6] == (byte)'+' || buffer[offset + count - 6] == (byte)'-'))
{
// zoneID = "+ | - hh:mm"
return count - 6;
}
// zoneID = "+ | - hh"
if (buffer[offset + count - 3] == (byte)'+' || buffer[offset + count - 3] == (byte)'-')
{
return count - 3;
}
// there is no time zone
return count;
}
private static int ParseNano(byte[] buffer, int offset, int offsetNano, int count)
{
var summaryOffset = offset + offsetNano;
if (count <= offsetNano + 3)
{
var order = offsetNano + 3 - count;
var milli = FixTypes.ParseNumberPart(buffer, summaryOffset, summaryOffset + 3 - order);
while (order > 0)
{
milli *= 10;
order--;
}
if (milli < 0 || milli > 999)
{
throw new ArgumentException("Incorrect milli value");
}
return milli * 1000000;
}
if (count <= offsetNano + 6)
{
var order = offsetNano + 6 - count;
var micro = FixTypes.ParseNumberPart(buffer, summaryOffset, summaryOffset + 6 - order);
while (order > 0)
{
micro *= 10;
order--;
}
if (micro < 0 || micro > 999999)
{
throw new ArgumentException("Incorrect micro value");
}
return micro * 1000;
}
if (count <= offsetNano + 9)
{
var order = offsetNano + 9 - count;
var nanosecond = FixTypes.ParseNumberPart(buffer, summaryOffset, summaryOffset + 9 - order);
while (order > 0)
{
nanosecond *= 10;
order--;
}
if (nanosecond < 0 || nanosecond > 999999999)
{
throw new ArgumentException("Incorrect nano value");
}
return nanosecond;
}
throw new ArgumentException("Invalid buffer length");
}
}
}