Spaces:
Sleeping
Sleeping
namespace google { | |
namespace protobuf { | |
namespace internal { | |
namespace { | |
static const int64 kSecondsPerMinute = 60; | |
static const int64 kSecondsPerHour = 3600; | |
static const int64 kSecondsPerDay = kSecondsPerHour * 24; | |
static const int64 kSecondsPer400Years = | |
kSecondsPerDay * (400 * 365 + 400 / 4 - 3); | |
// Seconds from 0001-01-01T00:00:00 to 1970-01-01T:00:00:00 | |
static const int64 kSecondsFromEraToEpoch = 62135596800LL; | |
// The range of timestamp values we support. | |
static const int64 kMinTime = -62135596800LL; // 0001-01-01T00:00:00 | |
static const int64 kMaxTime = 253402300799LL; // 9999-12-31T23:59:59 | |
static const int kNanosPerMillisecond = 1000000; | |
static const int kNanosPerMicrosecond = 1000; | |
// Count the seconds from the given year (start at Jan 1, 00:00) to 100 years | |
// after. | |
int64 SecondsPer100Years(int year) { | |
if (year % 400 == 0 || year % 400 > 300) { | |
return kSecondsPerDay * (100 * 365 + 100 / 4); | |
} else { | |
return kSecondsPerDay * (100 * 365 + 100 / 4 - 1); | |
} | |
} | |
// Count the seconds from the given year (start at Jan 1, 00:00) to 4 years | |
// after. | |
int64 SecondsPer4Years(int year) { | |
if ((year % 100 == 0 || year % 100 > 96) && | |
!(year % 400 == 0 || year % 400 > 396)) { | |
// No leap years. | |
return kSecondsPerDay * (4 * 365); | |
} else { | |
// One leap years. | |
return kSecondsPerDay * (4 * 365 + 1); | |
} | |
} | |
bool IsLeapYear(int year) { | |
return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); | |
} | |
int64 SecondsPerYear(int year) { | |
return kSecondsPerDay * (IsLeapYear(year) ? 366 : 365); | |
} | |
static const int kDaysInMonth[13] = { | |
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 | |
}; | |
int64 SecondsPerMonth(int month, bool leap) { | |
if (month == 2 && leap) { | |
return kSecondsPerDay * (kDaysInMonth[month] + 1); | |
} | |
return kSecondsPerDay * kDaysInMonth[month]; | |
} | |
static const int kDaysSinceJan[13] = { | |
0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, | |
}; | |
bool ValidateDateTime(const DateTime& time) { | |
if (time.year < 1 || time.year > 9999 || | |
time.month < 1 || time.month > 12 || | |
time.day < 1 || time.day > 31 || | |
time.hour < 0 || time.hour > 23 || | |
time.minute < 0 || time.minute > 59 || | |
time.second < 0 || time.second > 59) { | |
return false; | |
} | |
if (time.month == 2 && IsLeapYear(time.year)) { | |
return time.day <= kDaysInMonth[time.month] + 1; | |
} else { | |
return time.day <= kDaysInMonth[time.month]; | |
} | |
} | |
// Count the number of seconds elapsed from 0001-01-01T00:00:00 to the given | |
// time. | |
int64 SecondsSinceCommonEra(const DateTime& time) { | |
int64 result = 0; | |
// Years should be between 1 and 9999. | |
assert(time.year >= 1 && time.year <= 9999); | |
int year = 1; | |
if ((time.year - year) >= 400) { | |
int count_400years = (time.year - year) / 400; | |
result += kSecondsPer400Years * count_400years; | |
year += count_400years * 400; | |
} | |
while ((time.year - year) >= 100) { | |
result += SecondsPer100Years(year); | |
year += 100; | |
} | |
while ((time.year - year) >= 4) { | |
result += SecondsPer4Years(year); | |
year += 4; | |
} | |
while (time.year > year) { | |
result += SecondsPerYear(year); | |
++year; | |
} | |
// Months should be between 1 and 12. | |
assert(time.month >= 1 && time.month <= 12); | |
int month = time.month; | |
result += kSecondsPerDay * kDaysSinceJan[month]; | |
if (month > 2 && IsLeapYear(year)) { | |
result += kSecondsPerDay; | |
} | |
assert(time.day >= 1 && | |
time.day <= (month == 2 && IsLeapYear(year) | |
? kDaysInMonth[month] + 1 | |
: kDaysInMonth[month])); | |
result += kSecondsPerDay * (time.day - 1); | |
result += kSecondsPerHour * time.hour + | |
kSecondsPerMinute * time.minute + | |
time.second; | |
return result; | |
} | |
// Format nanoseconds with either 3, 6, or 9 digits depending on the required | |
// precision to represent the exact value. | |
std::string FormatNanos(int32 nanos) { | |
if (nanos % kNanosPerMillisecond == 0) { | |
return StringPrintf("%03d", nanos / kNanosPerMillisecond); | |
} else if (nanos % kNanosPerMicrosecond == 0) { | |
return StringPrintf("%06d", nanos / kNanosPerMicrosecond); | |
} else { | |
return StringPrintf("%09d", nanos); | |
} | |
} | |
// Parses an integer from a null-terminated char sequence. The method | |
// consumes at most "width" chars. Returns a pointer after the consumed | |
// integer, or nullptr if the data does not start with an integer or the | |
// integer value does not fall in the range of [min_value, max_value]. | |
const char* ParseInt(const char* data, int width, int min_value, | |
int max_value, int* result) { | |
if (!ascii_isdigit(*data)) { | |
return nullptr; | |
} | |
int value = 0; | |
for (int i = 0; i < width; ++i, ++data) { | |
if (ascii_isdigit(*data)) { | |
value = value * 10 + (*data - '0'); | |
} else { | |
break; | |
} | |
} | |
if (value >= min_value && value <= max_value) { | |
*result = value; | |
return data; | |
} else { | |
return nullptr; | |
} | |
} | |
// Consumes the fractional parts of a second into nanos. For example, | |
// "010" will be parsed to 10000000 nanos. | |
const char* ParseNanos(const char* data, int32* nanos) { | |
if (!ascii_isdigit(*data)) { | |
return nullptr; | |
} | |
int value = 0; | |
int len = 0; | |
// Consume as many digits as there are but only take the first 9 into | |
// account. | |
while (ascii_isdigit(*data)) { | |
if (len < 9) { | |
value = value * 10 + *data - '0'; | |
} | |
++len; | |
++data; | |
} | |
while (len < 9) { | |
value = value * 10; | |
++len; | |
} | |
*nanos = value; | |
return data; | |
} | |
const char* ParseTimezoneOffset(const char* data, int64* offset) { | |
// Accept format "HH:MM". E.g., "08:00" | |
int hour; | |
if ((data = ParseInt(data, 2, 0, 23, &hour)) == nullptr) { | |
return nullptr; | |
} | |
if (*data++ != ':') { | |
return nullptr; | |
} | |
int minute; | |
if ((data = ParseInt(data, 2, 0, 59, &minute)) == nullptr) { | |
return nullptr; | |
} | |
*offset = (hour * 60 + minute) * 60; | |
return data; | |
} | |
} // namespace | |
bool SecondsToDateTime(int64 seconds, DateTime* time) { | |
if (seconds < kMinTime || seconds > kMaxTime) { | |
return false; | |
} | |
// It's easier to calculate the DateTime starting from 0001-01-01T00:00:00 | |
seconds = seconds + kSecondsFromEraToEpoch; | |
int year = 1; | |
if (seconds >= kSecondsPer400Years) { | |
int count_400years = seconds / kSecondsPer400Years; | |
year += 400 * count_400years; | |
seconds %= kSecondsPer400Years; | |
} | |
while (seconds >= SecondsPer100Years(year)) { | |
seconds -= SecondsPer100Years(year); | |
year += 100; | |
} | |
while (seconds >= SecondsPer4Years(year)) { | |
seconds -= SecondsPer4Years(year); | |
year += 4; | |
} | |
while (seconds >= SecondsPerYear(year)) { | |
seconds -= SecondsPerYear(year); | |
year += 1; | |
} | |
bool leap = IsLeapYear(year); | |
int month = 1; | |
while (seconds >= SecondsPerMonth(month, leap)) { | |
seconds -= SecondsPerMonth(month, leap); | |
++month; | |
} | |
int day = 1 + seconds / kSecondsPerDay; | |
seconds %= kSecondsPerDay; | |
int hour = seconds / kSecondsPerHour; | |
seconds %= kSecondsPerHour; | |
int minute = seconds / kSecondsPerMinute; | |
seconds %= kSecondsPerMinute; | |
time->year = year; | |
time->month = month; | |
time->day = day; | |
time->hour = hour; | |
time->minute = minute; | |
time->second = static_cast<int>(seconds); | |
return true; | |
} | |
bool DateTimeToSeconds(const DateTime& time, int64* seconds) { | |
if (!ValidateDateTime(time)) { | |
return false; | |
} | |
*seconds = SecondsSinceCommonEra(time) - kSecondsFromEraToEpoch; | |
return true; | |
} | |
void GetCurrentTime(int64* seconds, int32* nanos) { | |
// TODO(xiaofeng): Improve the accuracy of this implementation (or just | |
// remove this method from protobuf). | |
*seconds = time(nullptr); | |
*nanos = 0; | |
} | |
std::string FormatTime(int64 seconds, int32 nanos) { | |
DateTime time; | |
if (nanos < 0 || nanos > 999999999 || !SecondsToDateTime(seconds, &time)) { | |
return "InvalidTime"; | |
} | |
std::string result = | |
StringPrintf("%04d-%02d-%02dT%02d:%02d:%02d", time.year, time.month, | |
time.day, time.hour, time.minute, time.second); | |
if (nanos != 0) { | |
result += "." + FormatNanos(nanos); | |
} | |
return result + "Z"; | |
} | |
bool ParseTime(const std::string& value, int64* seconds, int32* nanos) { | |
DateTime time; | |
const char* data = value.c_str(); | |
// We only accept: | |
// Z-normalized: 2015-05-20T13:29:35.120Z | |
// With UTC offset: 2015-05-20T13:29:35.120-08:00 | |
// Parse year | |
if ((data = ParseInt(data, 4, 1, 9999, &time.year)) == nullptr) { | |
return false; | |
} | |
// Expect '-' | |
if (*data++ != '-') return false; | |
// Parse month | |
if ((data = ParseInt(data, 2, 1, 12, &time.month)) == nullptr) { | |
return false; | |
} | |
// Expect '-' | |
if (*data++ != '-') return false; | |
// Parse day | |
if ((data = ParseInt(data, 2, 1, 31, &time.day)) == nullptr) { | |
return false; | |
} | |
// Expect 'T' | |
if (*data++ != 'T') return false; | |
// Parse hour | |
if ((data = ParseInt(data, 2, 0, 23, &time.hour)) == nullptr) { | |
return false; | |
} | |
// Expect ':' | |
if (*data++ != ':') return false; | |
// Parse minute | |
if ((data = ParseInt(data, 2, 0, 59, &time.minute)) == nullptr) { | |
return false; | |
} | |
// Expect ':' | |
if (*data++ != ':') return false; | |
// Parse second | |
if ((data = ParseInt(data, 2, 0, 59, &time.second)) == nullptr) { | |
return false; | |
} | |
if (!DateTimeToSeconds(time, seconds)) { | |
return false; | |
} | |
// Parse nanoseconds. | |
if (*data == '.') { | |
++data; | |
// Parse nanoseconds. | |
if ((data = ParseNanos(data, nanos)) == nullptr) { | |
return false; | |
} | |
} else { | |
*nanos = 0; | |
} | |
// Parse UTC offsets. | |
if (*data == 'Z') { | |
++data; | |
} else if (*data == '+') { | |
++data; | |
int64 offset; | |
if ((data = ParseTimezoneOffset(data, &offset)) == nullptr) { | |
return false; | |
} | |
*seconds -= offset; | |
} else if (*data == '-') { | |
++data; | |
int64 offset; | |
if ((data = ParseTimezoneOffset(data, &offset)) == nullptr) { | |
return false; | |
} | |
*seconds += offset; | |
} else { | |
return false; | |
} | |
// Done with parsing. | |
return *data == 0; | |
} | |
} // namespace internal | |
} // namespace protobuf | |
} // namespace google | |