File: System\Formats\Asn1\AsnWriter.UtcTime.cs
Web Access
Project: src\src\libraries\System.Formats.Asn1\src\System.Formats.Asn1.csproj (System.Formats.Asn1)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
 
namespace System.Formats.Asn1
{
    public sealed partial class AsnWriter
    {
        /// <summary>
        ///   Write the provided value as a UTCTime with a specified tag,
        ///   accepting the two-digit year as valid in context.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 23).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        /// <seealso cref="WriteUtcTime(DateTimeOffset,int,Nullable{Asn1Tag})"/>
        /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/>
        public void WriteUtcTime(DateTimeOffset value, Asn1Tag? tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.UtcTime);
 
            // Clear the constructed flag, if present.
            WriteUtcTimeCore(tag?.AsPrimitive() ?? Asn1Tag.UtcTime, value);
        }
 
        /// <summary>
        ///   Write the provided value as a UTCTime with a specified tag,
        ///   provided the year is in the allowed range.
        /// </summary>
        /// <param name="value">The value to write.</param>
        /// <param name="twoDigitYearMax">
        ///   The maximum valid year for <paramref name="value"/>, after conversion to UTC.
        ///   For the X.509 Time.utcTime range of 1950-2049, pass <c>2049</c>.
        /// </param>
        /// <param name="tag">The tag to write, or <see langword="null"/> for the default tag (Universal 23).</param>
        /// <exception cref="ArgumentException">
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="tag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="value"/>.<see cref="DateTimeOffset.Year"/> (after conversion to UTC)
        ///   is not in the range
        ///   (<paramref name="twoDigitYearMax"/> - 100, <paramref name="twoDigitYearMax"/>].
        /// </exception>
        /// <seealso cref="WriteUtcTime(DateTimeOffset,Nullable{Asn1Tag})"/>
        /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/>
        public void WriteUtcTime(DateTimeOffset value, int twoDigitYearMax, Asn1Tag? tag = null)
        {
            CheckUniversalTag(tag, UniversalTagNumber.UtcTime);
 
            value = value.ToUniversalTime();
 
            if (value.Year > twoDigitYearMax || value.Year <= twoDigitYearMax - 100)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }
 
            WriteUtcTimeCore(tag?.AsPrimitive() ?? Asn1Tag.UtcTime, value);
        }
 
        // T-REC-X.680-201508 sec 47
        // T-REC-X.690-201508 sec 11.8
        private void WriteUtcTimeCore(Asn1Tag tag, DateTimeOffset value)
        {
            // Because UtcTime is IMPLICIT VisibleString it technically can have
            // a constructed form.
            // DER says character strings must be primitive.
            // CER says character strings <= 1000 encoded bytes must be primitive.
            // So we'll just make BER be primitive, too.
            Debug.Assert(!tag.IsConstructed);
            WriteTag(tag);
 
            // BER allows for omitting the seconds, but that's not an option we need to expose.
            // BER allows for non-UTC values, but that's also not an option we need to expose.
            // So the format is always yyMMddHHmmssZ (13)
            const int UtcTimeValueLength = 13;
            WriteLength(UtcTimeValueLength);
 
            DateTimeOffset normalized = value.ToUniversalTime();
 
            int year = normalized.Year;
            int month = normalized.Month;
            int day = normalized.Day;
            int hour = normalized.Hour;
            int minute = normalized.Minute;
            int second = normalized.Second;
 
            Span<byte> baseSpan = _buffer.AsSpan(_offset);
            StandardFormat format = new StandardFormat('D', 2);
 
            if (!Utf8Formatter.TryFormat(year % 100, baseSpan.Slice(0, 2), out _, format) ||
                !Utf8Formatter.TryFormat(month, baseSpan.Slice(2, 2), out _, format) ||
                !Utf8Formatter.TryFormat(day, baseSpan.Slice(4, 2), out _, format) ||
                !Utf8Formatter.TryFormat(hour, baseSpan.Slice(6, 2), out _, format) ||
                !Utf8Formatter.TryFormat(minute, baseSpan.Slice(8, 2), out _, format) ||
                !Utf8Formatter.TryFormat(second, baseSpan.Slice(10, 2), out _, format))
            {
                Debug.Fail($"Utf8Formatter.TryFormat failed to build components of {normalized:O}");
                throw new InvalidOperationException();
            }
 
            _buffer[_offset + 12] = (byte)'Z';
 
            _offset += UtcTimeValueLength;
        }
    }
}