DateTime DateTime::Parse()

in sdk/core/azure-core/src/datetime.cpp [450:745]


DateTime DateTime::Parse(std::string const& dateTime, DateFormat format)
{
  // The values that are not supposed to be read before they are written are set to -123... to avoid
  // warnings on some compilers, yet provide a clearly bad value to make it obvious if things don't
  // work as expected.
  int16_t year = -12345;
  int8_t month = -123;
  int8_t day = -123;

  int8_t hour = 0;
  int8_t minute = 0;
  int8_t second = 0;
  int32_t fracSec = 0;
  int8_t dayOfWeek = -1;
  int8_t localDiffHours = 0;
  int8_t localDiffMinutes = 0;
  bool roundFracSecUp = false;
  {
    std::string::size_type const DateTimeLength = dateTime.length();
    std::string::size_type minDateTimeLength = 0;
    std::string::size_type cursor = 0;
    if (format == DateFormat::Rfc1123)
    {
      // Shortest possible string: "1 Jan 0001 00:00 UT"
      // Longest possible string: "Fri, 31 Dec 9999 23:59:60 +9959"
      IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 19);

      if (dateTime[cursor + 3] == ',')
      {
        dayOfWeek = SubstringEqualsAny(dateTime, cursor, 3, DayNames);
        if (dayOfWeek == -1 || dateTime[cursor + 4] != ' ')
        {
          ThrowParseError("day of week");
        }

        cursor += 5;
        IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 5);
      }

      {
        auto const parsingArea = "day";

        auto const oldCursor = cursor;
        day = ParseNumber<decltype(day)>(&cursor, dateTime, DateTimeLength, parsingArea, 1, 2);

        IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, (cursor - oldCursor) - 1);
        ParseSingleChar(&cursor, dateTime, parsingArea, ' ');
      }

      {
        month = 1 + SubstringEqualsAny(dateTime, cursor, 3, MonthNames);

        if (month == 0 || dateTime[cursor + 3] != ' ')
        {
          ThrowParseError("month");
        }

        cursor += 4;
      }

      {
        auto const parsingArea = "year";
        year = ParseNumber<decltype(year)>(&cursor, dateTime, DateTimeLength, parsingArea, 4, 4);
        ParseSingleChar(&cursor, dateTime, parsingArea, ' ');
      }

      {
        auto parsingArea = "hour and minute";

        hour = ParseNumber<decltype(hour)>(&cursor, dateTime, DateTimeLength, "hour", 2, 2);
        ParseSingleChar(&cursor, dateTime, parsingArea, ':');
        minute = ParseNumber<decltype(minute)>(&cursor, dateTime, DateTimeLength, "minute", 2, 2);

        if (dateTime[cursor] == ':')
        {
          ++cursor;
          parsingArea = "second";
          second
              = ParseNumber<decltype(second)>(&cursor, dateTime, DateTimeLength, parsingArea, 2, 2);

          IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 3);
        }
        ParseSingleChar(&cursor, dateTime, parsingArea, ' ');
      }

      if (!SubstringEquals(dateTime, cursor, 2, "UT"))
      {
        IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 1);
        if (!SubstringEquals(dateTime, cursor, 3, "GMT"))
        {
          static std::string const TimeZones[]
              = {"EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT"};
          static int8_t const HourAdjustments[] = {-5, -4, -6, -5, -7, -6, -8, -7};

          auto tz = SubstringEqualsAny(dateTime, cursor, 3, TimeZones);
          if (tz >= 0)
          {
            localDiffHours = HourAdjustments[tz];
          }
          else
          {
            IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 2);
            auto const parsingArea = "time zone";
            int8_t sign = 0;
            if (dateTime[cursor] == '+')
            {
              sign = +1;
            }
            else if (dateTime[cursor] == '-')
            {
              sign = -1;
            }
            else
            {
              ThrowParseError(parsingArea);
            }

            ++cursor;

            localDiffHours = sign
                * ParseNumber<decltype(localDiffHours)>(
                                 &cursor, dateTime, DateTimeLength, parsingArea, 2, 2);

            localDiffMinutes = sign
                * ParseNumber<decltype(localDiffMinutes)>(
                                   &cursor, dateTime, DateTimeLength, parsingArea, 2, 2);
          }
        }
      }
    }
    else if (format == DateFormat::Rfc3339)
    {
      // Shortest possible string: "00010101"
      // "Longest" possible string: "9999-12-31T23:59:60.1234567*+99:59"
      // * - any fractional second digits after the 7th are ignored.
      IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 8);

      {
        auto const parsingArea = "year";
        year = ParseNumber<decltype(year)>(&cursor, dateTime, DateTimeLength, parsingArea, 4, 4);
        ParseSingleOptionalChar(&cursor, dateTime, '-', &minDateTimeLength, DateTimeLength);
      }

      {
        auto const parsingArea = "month";
        month = ParseNumber<decltype(month)>(&cursor, dateTime, DateTimeLength, parsingArea, 2, 2);
        ParseSingleOptionalChar(&cursor, dateTime, '-', &minDateTimeLength, DateTimeLength);
      }

      {
        auto const parsingArea = "day";
        day = ParseNumber<decltype(day)>(&cursor, dateTime, DateTimeLength, parsingArea, 2, 2);
      }

      if (cursor < DateTimeLength
          && (dateTime[cursor] == 'T' || dateTime[cursor] == 't' || dateTime[cursor] == ' '))
      {
        ++cursor;
        IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 7);

        {
          auto const parsingArea = "hour";
          hour = ParseNumber<decltype(hour)>(&cursor, dateTime, DateTimeLength, parsingArea, 2, 2);
          ParseSingleOptionalChar(&cursor, dateTime, ':', &minDateTimeLength, DateTimeLength);
        }

        {
          auto const parsingArea = "minute";
          minute
              = ParseNumber<decltype(minute)>(&cursor, dateTime, DateTimeLength, parsingArea, 2, 2);
          ParseSingleOptionalChar(&cursor, dateTime, ':', &minDateTimeLength, DateTimeLength);
        }

        {
          auto const parsingArea = "second";
          second
              = ParseNumber<decltype(second)>(&cursor, dateTime, DateTimeLength, parsingArea, 2, 2);
        }

        if (cursor + 1 < DateTimeLength)
        {
          if (dateTime[cursor] == '.')
          {
            ++minDateTimeLength;
            ++cursor;
          }

          {
            auto oldCursor = cursor;
            fracSec = ParseNumber<decltype(fracSec)>(
                &cursor, dateTime, DateTimeLength, "second fraction", 0, 7);

            auto const charsRead = (cursor - oldCursor);
            {
              auto const zerosToAdd = static_cast<int>(7 - charsRead);
              for (auto i = 0; i < zerosToAdd; ++i)
              {
                fracSec *= 10;
              }
            }

            minDateTimeLength += charsRead;
            if (charsRead == 7 && (DateTimeLength - cursor) > 0)
            {
              auto const ch = dateTime[cursor];
              if (Core::_internal::StringExtensions::IsDigit(ch))
              {
                auto const num = static_cast<int>(static_cast<unsigned char>(ch) - '0');
                if (num > 4)
                {
                  if (fracSec < 9999999)
                  {
                    ++fracSec;
                  }
                  else
                  {
                    roundFracSecUp = true;
                  }
                }

                ++cursor;
              }
            }
          }

          for (auto i = DateTimeLength - cursor; i > 0; --i)
          {
            if (Core::_internal::StringExtensions::IsDigit(dateTime[cursor]))
            {
              ++minDateTimeLength;
              ++cursor;
            }
            else
            {
              break;
            }
          }

          if (DateTimeLength - cursor > 0)
          {
            auto const parsingArea = "time zone";
            int8_t sign = 0;
            if (dateTime[cursor] == '+')
            {
              sign = +1;
            }
            else if (dateTime[cursor] == '-')
            {
              sign = -1;
            }

            if (sign != 0)
            {
              ++cursor;
              IncreaseAndCheckMinLength(&minDateTimeLength, DateTimeLength, 6);

              localDiffHours = sign
                  * ParseNumber<decltype(localDiffHours)>(
                                   &cursor, dateTime, DateTimeLength, parsingArea, 2, 2);

              if (dateTime[cursor] == ':')
              {
                ++cursor;
              }
              else
              {
                ThrowParseError(parsingArea);
              }

              localDiffMinutes = sign
                  * ParseNumber<decltype(localDiffMinutes)>(
                                     &cursor, dateTime, DateTimeLength, parsingArea, 2, 2);
            }
          }
        }
      }
    }
    else
    {
      throw std::invalid_argument("Unrecognized date format.");
    }

    return DateTime(
        year,
        month,
        day,
        hour,
        minute,
        second,
        fracSec,
        dayOfWeek,
        localDiffHours,
        localDiffMinutes,
        roundFracSecUp);
  }
}