private static bool TryFormatInt64N(long value, byte precision, Span <byte> buffer, out int bytesWritten) { int digitCount = FormattingHelpers.CountDigits(value); int groupSeparators = (int)FormattingHelpers.DivMod(digitCount, Utf8Constants.GroupSize, out long firstGroup); if (firstGroup == 0) { firstGroup = 3; groupSeparators--; } int trailingZeros = (precision == StandardFormat.NoPrecision) ? 2 : precision; int idx = (int)((value >> 63) & 1) + digitCount + groupSeparators; bytesWritten = idx; if (trailingZeros > 0) { bytesWritten += trailingZeros + 1; // +1 for period. } if (buffer.Length < bytesWritten) { bytesWritten = 0; return(false); } ref byte utf8Bytes = ref MemoryMarshal.GetReference(buffer);
private static bool TryFormatUInt64D(ulong value, byte precision, Span <byte> buffer, out int bytesWritten) { if (value <= long.MaxValue) { return(TryFormatInt64D((long)value, precision, buffer, out bytesWritten)); } // Remove a single digit from the number. This will get it below long.MaxValue // Then we call the faster long version and follow-up with writing the last // digit. This ends up being faster by a factor of 2 than to just do the entire // operation using the unsigned versions. value = FormattingHelpers.DivMod(value, 10, out ulong lastDigit); if (precision != StandardFormat.NoPrecision && precision > 0) { precision -= 1; } if (!TryFormatInt64D((long)value, precision, buffer, out bytesWritten)) { return(false); } if (buffer.Length - 1 < bytesWritten) { bytesWritten = 0; return(false); } ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference();
private static bool TryFormatUInt64N(ulong value, byte precision, Span <byte> buffer, out int bytesWritten) { if (value <= long.MaxValue) { return(TryFormatInt64N((long)value, precision, buffer, out bytesWritten)); } // The ulong path is much slower than the long path here, so we are doing the last group // inside this method plus the zero padding but routing to the long version for the rest. value = FormattingHelpers.DivMod(value, 1000, out ulong lastGroup); if (!TryFormatInt64N((long)value, 0, buffer, out bytesWritten)) { return(false); } if (precision == StandardFormat.NoPrecision) { precision = 2; } int idx = bytesWritten; // Since this method routes entirely to the long version if the number is smaller than // long.MaxValue, we are guaranteed to need to write 3 more digits here before the set // of trailing zeros. bytesWritten += 4; // 3 digits + group separator if (precision > 0) { bytesWritten += precision + 1; // +1 for period. } if (buffer.Length < bytesWritten) { bytesWritten = 0; return(false); } ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference();
/// <summary> /// Formats a TimeSpan as a UTF8 string. /// </summary> /// <param name="value">Value to format</param> /// <param name="buffer">Buffer to write the UTF8-formatted value to</param> /// <param name="bytesWritten">Receives the length of the formatted text in bytes</param> /// <param name="format">The standard format to use</param> /// <returns> /// true for success. "bytesWritten" contains the length of the formatted text in bytes. /// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds. /// </returns> /// <remarks> /// Formats supported: /// c/t/T (default) [-][d.]hh:mm:ss[.fffffff] (constant format) /// G [-]d:hh:mm:ss.fffffff (general long) /// g [-][d:]h:mm:ss[.f[f[f[f[f[f[f[]]]]]]] (general short) /// </remarks> /// <exceptions> /// <cref>System.FormatException</cref> if the format is not valid for this data type. /// </exceptions> public static bool TryFormat(TimeSpan value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default) { char symbol = format.IsDefault ? 'c' : format.Symbol; switch (symbol) { case 'G': case 'g': case 'c': case 't': case 'T': { bool longForm = (symbol == 'G'); bool constant = (symbol == 't' || symbol == 'T' || symbol == 'c'); long ticks = value.Ticks; int days = (int)FormattingHelpers.DivMod(ticks, TimeSpan.TicksPerDay, out long timeLeft); bool showSign = false; if (ticks < 0) { showSign = true; days = -days; timeLeft = -timeLeft; } int hours = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerHour, out timeLeft); int minutes = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerMinute, out timeLeft); int seconds = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerSecond, out long fraction); int dayDigits = 0; int hourDigits = (constant || longForm || hours > 9) ? 2 : 1; int fractionDigits = 0; bytesWritten = hourDigits + 6; // [h]h:mm:ss if (showSign) { bytesWritten += 1; // [-] } if (longForm || days > 0) { dayDigits = FormattingHelpers.CountDigits(days); bytesWritten += dayDigits + 1; // [d'.'] } if (longForm || fraction > 0) { fractionDigits = (longForm || constant) ? Utf8Constants.DateTimeNumFractionDigits : FormattingHelpers.CountFractionDigits(fraction); bytesWritten += fractionDigits + 1; // ['.'fffffff] or ['.'FFFFFFF] for short-form } if (buffer.Length < bytesWritten) { bytesWritten = 0; return(false); } ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); int idx = 0; if (showSign) { Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Minus; } if (dayDigits > 0) { idx += FormattingHelpers.WriteDigits(days, dayDigits, ref utf8Bytes, idx); Unsafe.Add(ref utf8Bytes, idx++) = constant ? Utf8Constants.Period : Utf8Constants.Colon; } idx += FormattingHelpers.WriteDigits(hours, hourDigits, ref utf8Bytes, idx); Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Colon; idx += FormattingHelpers.WriteDigits(minutes, 2, ref utf8Bytes, idx); Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Colon; idx += FormattingHelpers.WriteDigits(seconds, 2, ref utf8Bytes, idx); if (fractionDigits > 0) { Unsafe.Add(ref utf8Bytes, idx++) = Utf8Constants.Period; idx += FormattingHelpers.WriteFractionDigits(fraction, fractionDigits, ref utf8Bytes, idx); } return(true); }
/// <summary> /// Formats a TimeSpan as a UTF8 string. /// </summary> /// <param name="value">Value to format</param> /// <param name="destination">Buffer to write the UTF8-formatted value to</param> /// <param name="bytesWritten">Receives the length of the formatted text in bytes</param> /// <param name="format">The standard format to use</param> /// <returns> /// true for success. "bytesWritten" contains the length of the formatted text in bytes. /// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds. /// </returns> /// <remarks> /// Formats supported: /// c/t/T (default) [-][d.]hh:mm:ss[.fffffff] (constant format) /// G [-]d:hh:mm:ss.fffffff (general long) /// g [-][d:][h]h:mm:ss[.f[f[f[f[f[f[f]]]]]] (general short) /// </remarks> /// <exceptions> /// <cref>System.FormatException</cref> if the format is not valid for this data type. /// </exceptions> public static bool TryFormat(TimeSpan value, Span <byte> destination, out int bytesWritten, StandardFormat format = default) { char symbol = FormattingHelpers.GetSymbolOrDefault(format, 'c'); switch (symbol) { case 'c': case 'G': case 'g': break; case 't': case 'T': symbol = 'c'; break; default: return(FormattingHelpers.TryFormatThrowFormatException(out bytesWritten)); } // First, calculate how large an output buffer is needed to hold the entire output. int requiredOutputLength = 8; // start with "hh:mm:ss" and adjust as necessary uint fraction; ulong totalSecondsRemaining; { // Turn this into a non-negative TimeSpan if possible. var ticks = value.Ticks; if (ticks < 0) { ticks = -ticks; if (ticks < 0) { Debug.Assert(ticks == long.MinValue /* -9223372036854775808 */); // We computed these ahead of time; they're straight from the decimal representation of Int64.MinValue. fraction = 4775808; totalSecondsRemaining = 922337203685; goto AfterComputeFraction; } } totalSecondsRemaining = FormattingHelpers.DivMod((ulong)Math.Abs(value.Ticks), TimeSpan.TicksPerSecond, out ulong fraction64); fraction = (uint)fraction64; } AfterComputeFraction: int fractionDigits = 0; if (symbol == 'c') { // Only write out the fraction if it's non-zero, and in that // case write out the entire fraction (all digits). if (fraction != 0) { fractionDigits = Utf8Constants.DateTimeNumFractionDigits; } } else if (symbol == 'G') { // Always write out the fraction, even if it's zero. fractionDigits = Utf8Constants.DateTimeNumFractionDigits; } else { // Only write out the fraction if it's non-zero, and in that // case write out only the most significant digits. if (fraction != 0) { fractionDigits = Utf8Constants.DateTimeNumFractionDigits - FormattingHelpers.CountDecimalTrailingZeros(fraction, out fraction); } } Debug.Assert(fraction < 10_000_000); // If we're going to write out a fraction, also need to write the leading decimal. if (fractionDigits != 0) { requiredOutputLength += fractionDigits + 1; } ulong totalMinutesRemaining = 0; ulong seconds = 0; if (totalSecondsRemaining > 0) { // Only compute minutes if the TimeSpan has an absolute value of >= 1 minute. totalMinutesRemaining = FormattingHelpers.DivMod(totalSecondsRemaining, 60 /* seconds per minute */, out seconds); } Debug.Assert(seconds < 60); ulong totalHoursRemaining = 0; ulong minutes = 0; if (totalMinutesRemaining > 0) { // Only compute hours if the TimeSpan has an absolute value of >= 1 hour. totalHoursRemaining = FormattingHelpers.DivMod(totalMinutesRemaining, 60 /* minutes per hour */, out minutes); } Debug.Assert(minutes < 60); // At this point, we can switch over to 32-bit divmod since the data has shrunk far enough. Debug.Assert(totalHoursRemaining <= uint.MaxValue); uint days = 0; uint hours = 0; if (totalHoursRemaining > 0) { // Only compute days if the TimeSpan has an absolute value of >= 1 day. days = FormattingHelpers.DivMod((uint)totalHoursRemaining, 24 /* hours per day */, out hours); } Debug.Assert(hours < 24); int hourDigits = 2; if (hours < 10 && symbol == 'g') { // Only writing a one-digit hour, not a two-digit hour hourDigits--; requiredOutputLength--; } int dayDigits = 0; if (days == 0) { if (symbol == 'G') { requiredOutputLength += 2; // for the leading "0:" dayDigits = 1; } } else { dayDigits = FormattingHelpers.CountDigits(days); requiredOutputLength += dayDigits + 1; // for the leading "d:" (or "d.") } if (value.Ticks < 0) { requiredOutputLength++; // for the leading '-' sign } if (destination.Length < requiredOutputLength) { bytesWritten = 0; return(false); } bytesWritten = requiredOutputLength; int idx = 0; // Write leading '-' if necessary if (value.Ticks < 0) { destination[idx++] = Utf8Constants.Minus; } // Write day (and separator) if necessary if (dayDigits > 0) { FormattingHelpers.WriteDigits(days, destination.Slice(idx, dayDigits)); idx += dayDigits; destination[idx++] = (symbol == 'c') ? Utf8Constants.Period : Utf8Constants.Colon; } // Write "[h]h:mm:ss" FormattingHelpers.WriteDigits(hours, destination.Slice(idx, hourDigits)); idx += hourDigits; destination[idx++] = Utf8Constants.Colon; FormattingHelpers.WriteDigits((uint)minutes, destination.Slice(idx, 2)); idx += 2; destination[idx++] = Utf8Constants.Colon; FormattingHelpers.WriteDigits((uint)seconds, destination.Slice(idx, 2)); idx += 2; // Write fraction (and separator) if necessary if (fractionDigits > 0) { destination[idx++] = Utf8Constants.Period; FormattingHelpers.WriteDigits(fraction, destination.Slice(idx, fractionDigits)); idx += fractionDigits; } // And we're done! Debug.Assert(idx == requiredOutputLength); return(true); }