FixAntenna/NetCore/Message/HighPrecisionDateTimeFormatters.cs (251 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.Common.Utils; namespace Epam.FixAntenna.NetCore.Message { /// <summary> /// Helper class for FIX date and time formats. /// /// Provides ability for formatting different types of date/time values /// to the buffer of bytes. It is also possible to work with high precision /// values: micro- and nanoseconds. /// /// </summary> internal class HighPrecisionDateTimeFormatters { /// <summary> /// Formats the value of <c>time</c> to the <c>UTCTimeOnly</c> format. /// <p/> /// The format for <c>UTCTimeOnly</c> is HH:MM:SS[.sss][sss][sss]. /// </summary> /// <param name="buffer"> the buffer of bytes to keep the <c>UTCTimeOnly</c> format </param> /// <param name="time"> the given time value </param> /// <param name="precision"> the desired time precision /// </param> /// <exception cref="ArgumentException"> </exception> public static void formatTimeOnly(byte[] buffer, DateTime time, TimestampPrecision precision) { formatTimeOnly(buffer, time, precision, 0); } /// <summary> /// Formats the value of <c>time</c> to the <c>UTCTimeOnly</c> format. /// <p/> /// The format for <c>UTCTimeOnly</c> is HH:MM:SS[.sss][sss][sss]. /// </summary> /// <param name="buffer"> the buffer of bytes to keep the <c>UTCTimeOnly</c> format </param> /// <param name="time"> the given time value </param> /// <param name="precision"> the desired time precision </param> /// <param name="offset"> the offset /// </param> /// <exception cref="ArgumentException"> </exception> public static void formatTimeOnly(byte[] buffer, DateTime time, TimestampPrecision precision, int offset) { offset = FormatTimeWithoutFractions(time.Hour, time.Minute, time.Second, buffer, offset); switch (precision) { case TimestampPrecision.Second: break; case TimestampPrecision.Milli: case TimestampPrecision.Micro: case TimestampPrecision.Nano: buffer[offset++] = (byte)'.'; offset = FormatSecondFractions(time.GetNanosecondsOfSecond(), buffer, offset, precision); break; default: throw new ArgumentException( "Only 'Second', 'Milli', 'Micro' and 'Nano' values of timestamp precision are available"); } } /// <summary> /// Formats the value of <c>time</c> to the <c>TZTimeOnly</c> format. /// <p/> /// The format for <c>TZTimeOnly</c> is HH:MM[:SS][.sss][sss][sss][Z | [ + | - hh[:mm]]]. /// </summary> /// <param name="buffer"> the buffer of bytes to keep the <c>TZTimeOnly</c> format </param> /// <param name="time"> the given time value </param> /// <param name="precision"> the desired time precision </param> public static void formatTZTimeOnly(byte[] buffer, DateTimeOffset time, TimestampPrecision precision) { formatTZTimeOnly(buffer, time, precision, 0); } /// <summary> /// Formats the value of <c>time</c> to the <c>TZTimeOnly</c> format. /// <p/> /// The format for <c>TZTimeOnly</c> is HH:MM[:SS][.sss][sss][sss][Z | [ + | - hh[:mm]]]. /// </summary> /// <param name="buffer"> the buffer of bytes to keep the <c>TZTimeOnly</c> format </param> /// <param name="time"> the given time value </param> /// <param name="precision"> the desired time precision </param> /// <param name="offset"> the offset </param> public static void formatTZTimeOnly(byte[] buffer, DateTimeOffset time, TimestampPrecision precision, int offset) { offset = FormatHour(time.Hour, buffer, offset); buffer[offset++] = (byte)':'; offset = FormatMinute(time.Minute, buffer, offset); switch (precision) { case TimestampPrecision.Minute: break; case TimestampPrecision.Second: buffer[offset++] = (byte)':'; offset = FormatSecond(time.Second, buffer, offset); break; case TimestampPrecision.Milli: case TimestampPrecision.Micro: case TimestampPrecision.Nano: buffer[offset++] = (byte)':'; offset = FormatSecond(time.Second, buffer, offset); buffer[offset++] = (byte)'.'; offset = FormatSecondFractions(time.GetNanosecondsOfSecond(), buffer, offset, precision); break; } offset = FormatOffset(time.Offset, buffer, offset); } /// <summary> /// Formats the value of <c>dateTime</c> to <c>Timestamp</c> format. /// <p/> /// The format of <c>Timestamp</c> is YYYYMMDD-HH:MM:SS[.sss][sss][sss]. /// </summary> /// <param name="buffer"> the buffer of bytes to keep the <c>Timestamp</c> format </param> /// <param name="dateTime"> the given date time value </param> /// <param name="precision"> the desired timestamp precision /// </param> /// <exception cref="ArgumentException"> </exception> public static void FormatTimestamp(byte[] buffer, DateTime dateTime, TimestampPrecision precision) { FormatTimestamp(buffer, dateTime, precision, 0); } /// <summary> /// Formats the value of <c>dateTime</c> to <c>Timestamp</c> format. /// <p/> /// The format of <c>Timestamp</c> is YYYYMMDD-HH:MM:SS[.sss][sss][sss]. /// </summary> /// <param name="buffer"> the buffer of bytes to keep the <c>Timestamp</c> format </param> /// <param name="dateTime"> the given date time value </param> /// <param name="precision"> the desired timestamp precision </param> /// <param name="offset"> the offset /// </param> /// <exception cref="ArgumentException"> </exception> public static void FormatTimestamp(byte[] buffer, DateTime dateTime, TimestampPrecision precision, int offset) { offset = FormatDate(dateTime.Year, dateTime.Month, dateTime.Day, buffer, offset); buffer[offset++] = (byte)'-'; offset = FormatTimeWithoutFractions(dateTime.Hour, dateTime.Minute, dateTime.Second, buffer, offset); switch (precision) { case TimestampPrecision.Second: break; case TimestampPrecision.Milli: case TimestampPrecision.Micro: case TimestampPrecision.Nano: buffer[offset++] = (byte)'.'; offset = FormatSecondFractions(dateTime.GetNanosecondsOfSecond(), buffer, offset, precision); break; default: throw new ArgumentException( "Only 'Second', 'Milli', 'Micro' and 'Nano' values of timestamp precision are available"); } } /// <summary> /// Formats the value of <c>dateTime</c> to the <c>TZTimestamp</c>} format. /// <p/> /// 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 to keep the <c>TZTimestamp</c> format </param> /// <param name="dateTime"> the given date time value </param> /// <param name="precision"> the desired timestamp precision </param> public static void FormatTzTimestamp(byte[] buffer, DateTimeOffset dateTime, TimestampPrecision precision) { FormatTzTimestamp(buffer, dateTime, precision, 0); } /// <summary> /// Formats the value of <c>dateTime</c> to the <c>TZTimestamp</c> format. /// <p/> /// 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 to keep the <c>TZTimestamp</c> format </param> /// <param name="dateTime"> the given date time value </param> /// <param name="precision"> the desired timestamp precision </param> /// <param name="offset"> the offset </param> public static void FormatTzTimestamp(byte[] buffer, DateTimeOffset dateTime, TimestampPrecision precision, int offset) { offset = FormatDate(dateTime.Year, dateTime.Month, dateTime.Day, buffer, offset); buffer[offset++] = (byte)'-'; offset = FormatHour(dateTime.Hour, buffer, offset); buffer[offset++] = (byte)':'; offset = FormatMinute(dateTime.Minute, buffer, offset); switch (precision) { case TimestampPrecision.Minute: break; case TimestampPrecision.Second: buffer[offset++] = (byte)':'; offset = FormatSecond(dateTime.Second, buffer, offset); break; case TimestampPrecision.Milli: case TimestampPrecision.Micro: case TimestampPrecision.Nano: buffer[offset++] = (byte)':'; offset = FormatSecond(dateTime.Second, buffer, offset); buffer[offset++] = (byte)'.'; offset = FormatSecondFractions(dateTime.GetNanosecondsOfSecond(), buffer, offset, precision); break; } offset = FormatOffset(dateTime.Offset, buffer, offset); } /// <summary> /// Formats the value of <c>dateTime</c> to Storage Timestamp format. /// <p/> /// The format of Storage Timestamp is "YYYYMMDD HH:MM:SS.sss[sss][sss] - ". /// </summary> /// <param name="buffer"> the buffer of bytes to keep the Storage Timestamp format </param> /// <param name="offset"> the offset </param> /// <param name="dateTime"> the given date time value </param> /// <param name="precision"> the desired timestamp precision /// </param> /// <exception cref="ArgumentException"> </exception> public static void FormatStorageTimestamp(byte[] buffer, int offset, DateTimeOffset dateTime, TimestampPrecision precision) { var localOffset = offset; localOffset = FormatDate(dateTime.Year, dateTime.Month, dateTime.Day, buffer, localOffset); buffer[localOffset++] = (byte)' '; localOffset = FormatTimeWithoutFractions(dateTime.Hour, dateTime.Minute, dateTime.Second, buffer, localOffset); buffer[localOffset++] = (byte)'.'; localOffset = FormatSecondFractions(dateTime.GetNanosecondsOfSecond(), buffer, localOffset, precision); buffer[localOffset++] = (byte)' '; buffer[localOffset++] = (byte)'-'; buffer[localOffset++] = (byte)' '; } private static int FormatYear(int year, byte[] buffer, int offset) { buffer[offset] = (byte)(year / 1000 % 10 + '0'); buffer[offset + 1] = (byte)(year / 100 % 10 + '0'); buffer[offset + 2] = (byte)(year / 10 % 10 + '0'); buffer[offset + 3] = (byte)(year % 10 + '0'); return offset + 4; } private static int FormatMonth(int month, byte[] buffer, int offset) { buffer[offset] = (byte)(month / 10 % 10 + '0'); buffer[offset + 1] = (byte)(month % 10 + '0'); return offset + 2; } private static int FormatDay(int day, byte[] buffer, int offset) { buffer[offset] = (byte)(day / 10 % 10 + '0'); buffer[offset + 1] = (byte)(day % 10 + '0'); return offset + 2; } private static int FormatDate(int year, int month, int day, byte[] buffer, int offset) { offset = FormatYear(year, buffer, offset); offset = FormatMonth(month, buffer, offset); offset = FormatDay(day, buffer, offset); return offset; } private static int FormatHour(int hour, byte[] buffer, int offset) { buffer[offset] = (byte)(hour / 10 % 10 + '0'); buffer[offset + 1] = (byte)(hour % 10 + '0'); return offset + 2; } private static int FormatMinute(int minute, byte[] buffer, int offset) { buffer[offset] = (byte)(minute / 10 % 10 + '0'); buffer[offset + 1] = (byte)(minute % 10 + '0'); return offset + 2; } private static int FormatSecond(int second, byte[] buffer, int offset) { buffer[offset] = (byte)(second / 10 % 10 + '0'); buffer[offset + 1] = (byte)(second % 10 + '0'); return offset + 2; } private static int FormatTimeWithoutFractions(int hour, int minute, int second, byte[] buffer, int offset) { offset = FormatHour(hour, buffer, offset); buffer[offset++] = (byte)':'; offset = FormatMinute(minute, buffer, offset); buffer[offset++] = (byte)':'; offset = FormatSecond(second, buffer, offset); return offset; } private static int FormatSecondFractions(int nano, byte[] buffer, int offset, TimestampPrecision precision) { var milli = nano / 1000000; var micro = nano / 1000; buffer[offset] = (byte)(milli / 100 + '0'); buffer[offset + 1] = (byte)(milli / 10 % 10 + '0'); buffer[offset + 2] = (byte)(milli % 10 + '0'); switch (precision) { case TimestampPrecision.Milli: return offset + 3; case TimestampPrecision.Micro: micro %= 1000; buffer[offset + 3] = (byte)(micro / 100 + '0'); buffer[offset + 4] = (byte)(micro / 10 % 10 + '0'); buffer[offset + 5] = (byte)(micro % 10 + '0'); return offset + 6; case TimestampPrecision.Nano: micro %= 1000; buffer[offset + 3] = (byte)(micro / 100 + '0'); buffer[offset + 4] = (byte)(micro / 10 % 10 + '0'); buffer[offset + 5] = (byte)(micro % 10 + '0'); nano %= 1000; buffer[offset + 6] = (byte)(nano / 100 + '0'); buffer[offset + 7] = (byte)(nano / 10 % 10 + '0'); buffer[offset + 8] = (byte)(nano % 10 + '0'); return offset + 9; default: throw new ArgumentException( "Only 'Milli', 'Micro' and 'Nano' values of second fractions precision are available"); } } private static int FormatOffset(TimeSpan zoneOffset, byte[] buffer, int offset) { var offsetMinutes = zoneOffset.GetTotalMinutes(); if (offsetMinutes == 0) { buffer[offset] = (byte)'Z'; return offset + 1; } if (offsetMinutes < 0) { buffer[offset] = (byte)'-'; offsetMinutes = -offsetMinutes; } else { buffer[offset] = (byte)'+'; } if (offsetMinutes % 60 == 0) { var val = offsetMinutes / 60; buffer[offset + 1] = (byte)(val / 10 % 10 + '0'); buffer[offset + 2] = (byte)(val % 10 + '0'); return offset + 3; } else { var val = offsetMinutes / 60; buffer[offset + 1] = (byte)(val / 10 % 10 + '0'); buffer[offset + 2] = (byte)(val % 10 + '0'); buffer[offset + 3] = (byte)':'; val = offsetMinutes % 60; buffer[offset + 4] = (byte)(val / 10 % 10 + '0'); buffer[offset + 5] = (byte)(val % 10 + '0'); return offset + 6; } } } }