Brian Silverman | 9c614bc | 2016-02-15 20:20:02 -0500 | [diff] [blame^] | 1 | #include <google/protobuf/stubs/time.h> |
| 2 | |
| 3 | #include <ctime> |
| 4 | |
| 5 | #include <google/protobuf/stubs/stringprintf.h> |
| 6 | #include <google/protobuf/stubs/strutil.h> |
| 7 | |
| 8 | namespace google { |
| 9 | namespace protobuf { |
| 10 | namespace internal { |
| 11 | |
| 12 | namespace { |
| 13 | static const int64 kSecondsPerMinute = 60; |
| 14 | static const int64 kSecondsPerHour = 3600; |
| 15 | static const int64 kSecondsPerDay = kSecondsPerHour * 24; |
| 16 | static const int64 kSecondsPer400Years = |
| 17 | kSecondsPerDay * (400 * 365 + 400 / 4 - 3); |
| 18 | // Seconds from 0001-01-01T00:00:00 to 1970-01-01T:00:00:00 |
| 19 | static const int64 kSecondsFromEraToEpoch = 62135596800LL; |
| 20 | // The range of timestamp values we support. |
| 21 | static const int64 kMinTime = -62135596800LL; // 0001-01-01T00:00:00 |
| 22 | static const int64 kMaxTime = 253402300799LL; // 9999-12-31T23:59:59 |
| 23 | |
| 24 | static const int kNanosPerMillisecond = 1000000; |
| 25 | static const int kNanosPerMicrosecond = 1000; |
| 26 | |
| 27 | // Count the seconds from the given year (start at Jan 1, 00:00) to 100 years |
| 28 | // after. |
| 29 | int64 SecondsPer100Years(int year) { |
| 30 | if (year % 400 == 0 || year % 400 > 300) { |
| 31 | return kSecondsPerDay * (100 * 365 + 100 / 4); |
| 32 | } else { |
| 33 | return kSecondsPerDay * (100 * 365 + 100 / 4 - 1); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | // Count the seconds from the given year (start at Jan 1, 00:00) to 4 years |
| 38 | // after. |
| 39 | int64 SecondsPer4Years(int year) { |
| 40 | if ((year % 100 == 0 || year % 100 > 96) && |
| 41 | !(year % 400 == 0 || year % 400 > 396)) { |
| 42 | // No leap years. |
| 43 | return kSecondsPerDay * (4 * 365); |
| 44 | } else { |
| 45 | // One leap years. |
| 46 | return kSecondsPerDay * (4 * 365 + 1); |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | bool IsLeapYear(int year) { |
| 51 | return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); |
| 52 | } |
| 53 | |
| 54 | int64 SecondsPerYear(int year) { |
| 55 | return kSecondsPerDay * (IsLeapYear(year) ? 366 : 365); |
| 56 | } |
| 57 | |
| 58 | static const int kDaysInMonth[13] = { |
| 59 | 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 |
| 60 | }; |
| 61 | |
| 62 | int64 SecondsPerMonth(int month, bool leap) { |
| 63 | if (month == 2 && leap) { |
| 64 | return kSecondsPerDay * (kDaysInMonth[month] + 1); |
| 65 | } |
| 66 | return kSecondsPerDay * kDaysInMonth[month]; |
| 67 | } |
| 68 | |
| 69 | static const int kDaysSinceJan[13] = { |
| 70 | 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, |
| 71 | }; |
| 72 | |
| 73 | bool ValidateDateTime(const DateTime& time) { |
| 74 | if (time.year < 1 || time.year > 9999 || |
| 75 | time.month < 1 || time.month > 12 || |
| 76 | time.day < 1 || time.day > 31 || |
| 77 | time.hour < 0 || time.hour > 23 || |
| 78 | time.minute < 0 || time.minute > 59 || |
| 79 | time.second < 0 || time.second > 59) { |
| 80 | return false; |
| 81 | } |
| 82 | if (time.month == 2 && IsLeapYear(time.year)) { |
| 83 | return time.month <= kDaysInMonth[time.month] + 1; |
| 84 | } else { |
| 85 | return time.month <= kDaysInMonth[time.month]; |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | // Count the number of seconds elapsed from 0001-01-01T00:00:00 to the given |
| 90 | // time. |
| 91 | int64 SecondsSinceCommonEra(const DateTime& time) { |
| 92 | int64 result = 0; |
| 93 | // Years should be between 1 and 9999. |
| 94 | assert(time.year >= 1 && time.year <= 9999); |
| 95 | int year = 1; |
| 96 | if ((time.year - year) >= 400) { |
| 97 | int count_400years = (time.year - year) / 400; |
| 98 | result += kSecondsPer400Years * count_400years; |
| 99 | year += count_400years * 400; |
| 100 | } |
| 101 | while ((time.year - year) >= 100) { |
| 102 | result += SecondsPer100Years(year); |
| 103 | year += 100; |
| 104 | } |
| 105 | while ((time.year - year) >= 4) { |
| 106 | result += SecondsPer4Years(year); |
| 107 | year += 4; |
| 108 | } |
| 109 | while (time.year > year) { |
| 110 | result += SecondsPerYear(year); |
| 111 | ++year; |
| 112 | } |
| 113 | // Months should be between 1 and 12. |
| 114 | assert(time.month >= 1 && time.month <= 12); |
| 115 | int month = time.month; |
| 116 | result += kSecondsPerDay * kDaysSinceJan[month]; |
| 117 | if (month > 2 && IsLeapYear(year)) { |
| 118 | result += kSecondsPerDay; |
| 119 | } |
| 120 | assert(time.day >= 1 && |
| 121 | time.day <= (month == 2 && IsLeapYear(year) |
| 122 | ? kDaysInMonth[month] + 1 |
| 123 | : kDaysInMonth[month])); |
| 124 | result += kSecondsPerDay * (time.day - 1); |
| 125 | result += kSecondsPerHour * time.hour + |
| 126 | kSecondsPerMinute * time.minute + |
| 127 | time.second; |
| 128 | return result; |
| 129 | } |
| 130 | |
| 131 | // Format nanoseconds with either 3, 6, or 9 digits depending on the required |
| 132 | // precision to represent the exact value. |
| 133 | string FormatNanos(int32 nanos) { |
| 134 | if (nanos % kNanosPerMillisecond == 0) { |
| 135 | return StringPrintf("%03d", nanos / kNanosPerMillisecond); |
| 136 | } else if (nanos % kNanosPerMicrosecond == 0) { |
| 137 | return StringPrintf("%06d", nanos / kNanosPerMicrosecond); |
| 138 | } else { |
| 139 | return StringPrintf("%09d", nanos); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // Parses an integer from a null-terminated char sequence. The method |
| 144 | // consumes at most "width" chars. Returns a pointer after the consumed |
| 145 | // integer, or NULL if the data does not start with an integer or the |
| 146 | // integer value does not fall in the range of [min_value, max_value]. |
| 147 | const char* ParseInt(const char* data, int width, int min_value, |
| 148 | int max_value, int* result) { |
| 149 | if (!ascii_isdigit(*data)) { |
| 150 | return NULL; |
| 151 | } |
| 152 | int value = 0; |
| 153 | for (int i = 0; i < width; ++i, ++data) { |
| 154 | if (ascii_isdigit(*data)) { |
| 155 | value = value * 10 + (*data - '0'); |
| 156 | } else { |
| 157 | break; |
| 158 | } |
| 159 | } |
| 160 | if (value >= min_value && value <= max_value) { |
| 161 | *result = value; |
| 162 | return data; |
| 163 | } else { |
| 164 | return NULL; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | // Consumes the fractional parts of a second into nanos. For example, |
| 169 | // "010" will be parsed to 10000000 nanos. |
| 170 | const char* ParseNanos(const char* data, int32* nanos) { |
| 171 | if (!ascii_isdigit(*data)) { |
| 172 | return NULL; |
| 173 | } |
| 174 | int value = 0; |
| 175 | int len = 0; |
| 176 | // Consume as many digits as there are but only take the first 9 into |
| 177 | // account. |
| 178 | while (ascii_isdigit(*data)) { |
| 179 | if (len < 9) { |
| 180 | value = value * 10 + *data - '0'; |
| 181 | } |
| 182 | ++len; |
| 183 | ++data; |
| 184 | } |
| 185 | while (len < 9) { |
| 186 | value = value * 10; |
| 187 | ++len; |
| 188 | } |
| 189 | *nanos = value; |
| 190 | return data; |
| 191 | } |
| 192 | |
| 193 | const char* ParseTimezoneOffset(const char* data, int64* offset) { |
| 194 | // Accept format "HH:MM". E.g., "08:00" |
| 195 | int hour; |
| 196 | if ((data = ParseInt(data, 2, 0, 23, &hour)) == NULL) { |
| 197 | return NULL; |
| 198 | } |
| 199 | if (*data++ != ':') { |
| 200 | return NULL; |
| 201 | } |
| 202 | int minute; |
| 203 | if ((data = ParseInt(data, 2, 0, 59, &minute)) == NULL) { |
| 204 | return NULL; |
| 205 | } |
| 206 | *offset = (hour * 60 + minute) * 60; |
| 207 | return data; |
| 208 | } |
| 209 | } // namespace |
| 210 | |
| 211 | bool SecondsToDateTime(int64 seconds, DateTime* time) { |
| 212 | if (seconds < kMinTime || seconds > kMaxTime) { |
| 213 | return false; |
| 214 | } |
| 215 | // It's easier to calcuate the DateTime starting from 0001-01-01T00:00:00 |
| 216 | seconds = seconds + kSecondsFromEraToEpoch; |
| 217 | int year = 1; |
| 218 | if (seconds >= kSecondsPer400Years) { |
| 219 | int count_400years = seconds / kSecondsPer400Years; |
| 220 | year += 400 * count_400years; |
| 221 | seconds %= kSecondsPer400Years; |
| 222 | } |
| 223 | while (seconds >= SecondsPer100Years(year)) { |
| 224 | seconds -= SecondsPer100Years(year); |
| 225 | year += 100; |
| 226 | } |
| 227 | while (seconds >= SecondsPer4Years(year)) { |
| 228 | seconds -= SecondsPer4Years(year); |
| 229 | year += 4; |
| 230 | } |
| 231 | while (seconds >= SecondsPerYear(year)) { |
| 232 | seconds -= SecondsPerYear(year); |
| 233 | year += 1; |
| 234 | } |
| 235 | bool leap = IsLeapYear(year); |
| 236 | int month = 1; |
| 237 | while (seconds >= SecondsPerMonth(month, leap)) { |
| 238 | seconds -= SecondsPerMonth(month, leap); |
| 239 | ++month; |
| 240 | } |
| 241 | int day = 1 + seconds / kSecondsPerDay; |
| 242 | seconds %= kSecondsPerDay; |
| 243 | int hour = seconds / kSecondsPerHour; |
| 244 | seconds %= kSecondsPerHour; |
| 245 | int minute = seconds / kSecondsPerMinute; |
| 246 | seconds %= kSecondsPerMinute; |
| 247 | time->year = year; |
| 248 | time->month = month; |
| 249 | time->day = day; |
| 250 | time->hour = hour; |
| 251 | time->minute = minute; |
| 252 | time->second = static_cast<int>(seconds); |
| 253 | return true; |
| 254 | } |
| 255 | |
| 256 | bool DateTimeToSeconds(const DateTime& time, int64* seconds) { |
| 257 | if (!ValidateDateTime(time)) { |
| 258 | return false; |
| 259 | } |
| 260 | *seconds = SecondsSinceCommonEra(time) - kSecondsFromEraToEpoch; |
| 261 | return true; |
| 262 | } |
| 263 | |
| 264 | void GetCurrentTime(int64* seconds, int32* nanos) { |
| 265 | // TODO(xiaofeng): Improve the accuracy of this implementation (or just |
| 266 | // remove this method from protobuf). |
| 267 | *seconds = time(NULL); |
| 268 | *nanos = 0; |
| 269 | } |
| 270 | |
| 271 | string FormatTime(int64 seconds, int32 nanos) { |
| 272 | DateTime time; |
| 273 | if (nanos < 0 || nanos > 999999999 || !SecondsToDateTime(seconds, &time)) { |
| 274 | return "InvalidTime"; |
| 275 | } |
| 276 | string result = StringPrintf("%04d-%02d-%02dT%02d:%02d:%02d", |
| 277 | time.year, time.month, time.day, |
| 278 | time.hour, time.minute, time.second); |
| 279 | if (nanos != 0) { |
| 280 | result += "." + FormatNanos(nanos); |
| 281 | } |
| 282 | return result + "Z"; |
| 283 | } |
| 284 | |
| 285 | bool ParseTime(const string& value, int64* seconds, int32* nanos) { |
| 286 | DateTime time; |
| 287 | const char* data = value.c_str(); |
| 288 | // We only accept: |
| 289 | // Z-normalized: 2015-05-20T13:29:35.120Z |
| 290 | // With UTC offset: 2015-05-20T13:29:35.120-08:00 |
| 291 | |
| 292 | // Parse year |
| 293 | if ((data = ParseInt(data, 4, 1, 9999, &time.year)) == NULL) { |
| 294 | return false; |
| 295 | } |
| 296 | // Expect '-' |
| 297 | if (*data++ != '-') return false; |
| 298 | // Parse month |
| 299 | if ((data = ParseInt(data, 2, 1, 12, &time.month)) == NULL) { |
| 300 | return false; |
| 301 | } |
| 302 | // Expect '-' |
| 303 | if (*data++ != '-') return false; |
| 304 | // Parse day |
| 305 | if ((data = ParseInt(data, 2, 1, 31, &time.day)) == NULL) { |
| 306 | return false; |
| 307 | } |
| 308 | // Expect 'T' |
| 309 | if (*data++ != 'T') return false; |
| 310 | // Parse hour |
| 311 | if ((data = ParseInt(data, 2, 0, 23, &time.hour)) == NULL) { |
| 312 | return false; |
| 313 | } |
| 314 | // Expect ':' |
| 315 | if (*data++ != ':') return false; |
| 316 | // Parse minute |
| 317 | if ((data = ParseInt(data, 2, 0, 59, &time.minute)) == NULL) { |
| 318 | return false; |
| 319 | } |
| 320 | // Expect ':' |
| 321 | if (*data++ != ':') return false; |
| 322 | // Parse second |
| 323 | if ((data = ParseInt(data, 2, 0, 59, &time.second)) == NULL) { |
| 324 | return false; |
| 325 | } |
| 326 | if (!DateTimeToSeconds(time, seconds)) { |
| 327 | return false; |
| 328 | } |
| 329 | // Parse nanoseconds. |
| 330 | if (*data == '.') { |
| 331 | ++data; |
| 332 | // Parse nanoseconds. |
| 333 | if ((data = ParseNanos(data, nanos)) == NULL) { |
| 334 | return false; |
| 335 | } |
| 336 | } else { |
| 337 | *nanos = 0; |
| 338 | } |
| 339 | // Parse UTC offsets. |
| 340 | if (*data == 'Z') { |
| 341 | ++data; |
| 342 | } else if (*data == '+') { |
| 343 | ++data; |
| 344 | int64 offset; |
| 345 | if ((data = ParseTimezoneOffset(data, &offset)) == NULL) { |
| 346 | return false; |
| 347 | } |
| 348 | *seconds -= offset; |
| 349 | } else if (*data == '-') { |
| 350 | ++data; |
| 351 | int64 offset; |
| 352 | if ((data = ParseTimezoneOffset(data, &offset)) == NULL) { |
| 353 | return false; |
| 354 | } |
| 355 | *seconds += offset; |
| 356 | } else { |
| 357 | return false; |
| 358 | } |
| 359 | // Done with parsing. |
| 360 | return *data == 0; |
| 361 | } |
| 362 | |
| 363 | } // namespace internal |
| 364 | } // namespace protobuf |
| 365 | } // namespace google |