| /** |
| * Copyright 2011-2015 Quickstep Technologies LLC. |
| * Copyright 2015 Pivotal Software, Inc. |
| * |
| * 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. |
| **/ |
| |
| #ifndef __STDC_FORMAT_MACROS |
| #define __STDC_FORMAT_MACROS |
| #endif |
| |
| #include "types/DatetimeType.hpp" |
| |
| #include <cinttypes> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <ctime> |
| #include <string> |
| |
| #include "types/DatetimeLit.hpp" |
| #include "types/NullCoercibilityCheckMacro.hpp" |
| #include "types/Type.hpp" |
| #include "types/TypeID.hpp" |
| #include "types/TypedValue.hpp" |
| #include "types/port/gmtime_r.hpp" |
| #include "types/port/timegm.hpp" |
| #include "utility/CheckSnprintf.hpp" |
| |
| #include "glog/logging.h" |
| |
| // NetBSD's libc has snprintf, but it doesn't show up in the std namespace for |
| // C++. |
| #ifndef __NetBSD__ |
| using std::snprintf; |
| #endif |
| |
| namespace quickstep { |
| |
| bool DatetimeType::isCoercibleFrom(const Type &original_type) const { |
| QUICKSTEP_NULL_COERCIBILITY_CHECK(); |
| return (original_type.getTypeID() == kDatetime); |
| } |
| |
| bool DatetimeType::isSafelyCoercibleFrom(const Type &original_type) const { |
| QUICKSTEP_NULL_COERCIBILITY_CHECK(); |
| return (original_type.getTypeID() == kDatetime); |
| } |
| |
| std::string DatetimeType::printValueToString(const TypedValue &value) const { |
| DCHECK(!value.isNull()); |
| |
| const DatetimeLit literal = value.getLiteral<DatetimeLit>(); |
| const std::time_t timestamp = literal.epochTime(); |
| struct tm timeinfo; |
| quickstep::gmtime_r(×tamp, &timeinfo); |
| const std::int64_t subseconds = literal.subseconds(); |
| |
| // TODO(chasseur): std::put_time from C++11 makes it easy to write out |
| // ISO-8601 formatted dates, but unfortunately it is not yet implemented in |
| // GCC 4.8.3. |
| |
| // Note that although there is no year 0 in the Gregorian calendar, ISO 8601 |
| // has year 0 equivalent to 1 BCE, year -1 equivalent to 2 BCE, and so on. |
| std::int64_t actual_year = INT64_C(1900) + timeinfo.tm_year; |
| |
| char datebuf[DatetimeLit::kIsoChars + 1]; |
| std::size_t chars_written = 0; |
| int snprintf_result = 0; |
| |
| // ISO 8601 requires that "expanded" year ranges (> 4 digits or before year |
| // 0) are prefixed with a plus or minus. |
| if ((actual_year > 9999) || (actual_year < 0)) { |
| snprintf_result = snprintf(datebuf, sizeof(datebuf), "%+05" PRId64 "-", actual_year); |
| } else { |
| snprintf_result = snprintf(datebuf, sizeof(datebuf), "%04" PRId64 "-", actual_year); |
| } |
| CheckSnprintf(snprintf_result, sizeof(datebuf), &chars_written); |
| |
| // All the rest of the ISO 8601 date/time parts. |
| snprintf_result = snprintf(datebuf + chars_written, |
| sizeof(datebuf) - chars_written, |
| "%02d-%02dT%02d:%02d:%02d", |
| timeinfo.tm_mon + 1, |
| timeinfo.tm_mday, |
| timeinfo.tm_hour, |
| timeinfo.tm_min, |
| timeinfo.tm_sec); |
| CheckSnprintf(snprintf_result, sizeof(datebuf), &chars_written); |
| |
| // Fractional subseconds, if any. |
| if (subseconds != 0) { |
| snprintf_result = snprintf(datebuf + chars_written, |
| sizeof(datebuf) - chars_written, |
| ".%06" PRId64, |
| subseconds); |
| CheckSnprintf(snprintf_result, sizeof(datebuf), &chars_written); |
| } |
| |
| return std::string(datebuf); |
| } |
| |
| void DatetimeType::printValueToFile(const TypedValue &value, |
| FILE *file, |
| const int padding) const { |
| // We simply re-use the logic from printValueToString(), as trying to do |
| // padding on-the fly with so many different fields is too much of a hassle. |
| std::fprintf(file, "%*s", padding, printValueToString(value).c_str()); |
| } |
| |
| bool DatetimeType::parseValueFromString(const std::string &value_string, |
| TypedValue *value) const { |
| int year; |
| int month; |
| int day; |
| char separator; |
| int hour; |
| int minute; |
| int second; |
| char subsecond_buffer[7]; |
| |
| int date_chars = 0; |
| int date_time_chars = 0; |
| int date_time_subseconds_chars = 0; |
| |
| int matched = std::sscanf(value_string.c_str(), |
| "%d-%2d-%2d%n%c%2d:%2d:%2d%n.%6[0-9]%n", |
| &year, &month, &day, &date_chars, |
| &separator, &hour, &minute, &second, &date_time_chars, |
| subsecond_buffer, &date_time_subseconds_chars); |
| |
| // Check that the string specified date, date + time, or date + time with |
| // optional subseconds, then check that there is no unmatched garbage at the |
| // end of the string. |
| if (matched == 3) { |
| if (static_cast<std::string::size_type>(date_chars) |
| != value_string.length()) { |
| return false; |
| } |
| } else if (matched == 7) { |
| if (static_cast<std::string::size_type>(date_time_chars) |
| != value_string.length()) { |
| return false; |
| } |
| } else if (matched == 8) { |
| if (static_cast<std::string::size_type>(date_time_subseconds_chars) |
| != value_string.length()) { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| |
| // Validate month. |
| if ((month < 1) || (month > 12)) { |
| return false; |
| } |
| |
| // Validate day-of-month. |
| if (day < 1) { |
| return false; |
| } |
| switch (month) { |
| case 2: { |
| const int days_in_february |
| = ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)) |
| ? 29 : 28; |
| if (day > days_in_february) { |
| return false; |
| } |
| break; |
| } |
| case 4: // Fall-through for 30-day months. |
| case 6: |
| case 9: |
| case 11: |
| if (day > 30) { |
| return false; |
| } |
| break; |
| default: |
| if (day > 31) { |
| return false; |
| } |
| break; |
| } |
| |
| // Broken-down time counts from midnight, January 1st, 1900. |
| struct tm time_parts = {}; |
| time_parts.tm_year = year - 1900; |
| time_parts.tm_mon = month - 1; |
| time_parts.tm_mday = day; |
| |
| if (matched >= 7) { |
| if (!((separator == 'T') || (separator == 't') || (separator == ' '))) { |
| return false; |
| } |
| |
| if ((hour < 0) || (hour > 23)) { |
| return false; |
| } |
| |
| if ((minute < 0) || (minute > 59)) { |
| return false; |
| } |
| |
| if ((second < 0) || (second > 59)) { |
| // TODO(chasseur): Fix to account for leap-seconds. |
| return false; |
| } |
| |
| time_parts.tm_hour = hour; |
| time_parts.tm_min = minute; |
| time_parts.tm_sec = second; |
| } |
| |
| std::time_t epoch_time = quickstep::timegm(&time_parts); |
| if (epoch_time == static_cast<std::time_t>(-1)) { |
| // Special case: the last second of 1969 actually was one second before the |
| // epoch. |
| if (!((year == 1969) |
| && (month == 12) |
| && (day == 31) |
| && (time_parts.tm_hour = 23) |
| && (time_parts.tm_min = 59) |
| && (time_parts.tm_sec == 59))) { |
| return false; |
| } |
| } |
| |
| std::int64_t subseconds = 0; |
| if (matched == 8) { |
| const std::size_t subsecond_digits = std::strlen(subsecond_buffer); |
| if (subsecond_digits == 0) { |
| return false; |
| } |
| |
| subseconds = std::atoi(subsecond_buffer); |
| if (subseconds < 0) { |
| return false; |
| } |
| |
| for (std::size_t adjustment_pos = subsecond_digits; |
| adjustment_pos < 6; |
| ++adjustment_pos) { |
| subseconds *= 10; |
| } |
| } |
| |
| *value = TypedValue(DatetimeLit::FromEpochTimePlusSubseconds(epoch_time, subseconds)); |
| return true; |
| } |
| |
| } // namespace quickstep |