1// Copyright 2016 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "time_zone_posix.h"
16
17#include <cstddef>
18#include <cstring>
19#include <limits>
20#include <string>
21
22namespace absl {
23namespace time_internal {
24namespace cctz {
25
26namespace {
27
28const char kDigits[] = "0123456789";
29
30const char* ParseInt(const char* p, int min, int max, int* vp) {
31 int value = 0;
32 const char* op = p;
33 const int kMaxInt = std::numeric_limits<int>::max();
34 for (; const char* dp = strchr(kDigits, *p); ++p) {
35 int d = static_cast<int>(dp - kDigits);
36 if (d >= 10) break; // '\0'
37 if (value > kMaxInt / 10) return nullptr;
38 value *= 10;
39 if (value > kMaxInt - d) return nullptr;
40 value += d;
41 }
42 if (p == op || value < min || value > max) return nullptr;
43 *vp = value;
44 return p;
45}
46
47// abbr = <.*?> | [^-+,\d]{3,}
48const char* ParseAbbr(const char* p, std::string* abbr) {
49 const char* op = p;
50 if (*p == '<') { // special zoneinfo <...> form
51 while (*++p != '>') {
52 if (*p == '\0') return nullptr;
53 }
54 abbr->assign(op + 1, static_cast<std::size_t>(p - op) - 1);
55 return ++p;
56 }
57 while (*p != '\0') {
58 if (strchr("-+,", *p)) break;
59 if (strchr(kDigits, *p)) break;
60 ++p;
61 }
62 if (p - op < 3) return nullptr;
63 abbr->assign(op, static_cast<std::size_t>(p - op));
64 return p;
65}
66
67// offset = [+|-]hh[:mm[:ss]] (aggregated into single seconds value)
68const char* ParseOffset(const char* p, int min_hour, int max_hour, int sign,
69 std::int_fast32_t* offset) {
70 if (p == nullptr) return nullptr;
71 if (*p == '+' || *p == '-') {
72 if (*p++ == '-') sign = -sign;
73 }
74 int hours = 0;
75 int minutes = 0;
76 int seconds = 0;
77
78 p = ParseInt(p, min_hour, max_hour, &hours);
79 if (p == nullptr) return nullptr;
80 if (*p == ':') {
81 p = ParseInt(p + 1, 0, 59, &minutes);
82 if (p == nullptr) return nullptr;
83 if (*p == ':') {
84 p = ParseInt(p + 1, 0, 59, &seconds);
85 if (p == nullptr) return nullptr;
86 }
87 }
88 *offset = sign * ((((hours * 60) + minutes) * 60) + seconds);
89 return p;
90}
91
92// datetime = ( Jn | n | Mm.w.d ) [ / offset ]
93const char* ParseDateTime(const char* p, PosixTransition* res) {
94 if (p != nullptr && *p == ',') {
95 if (*++p == 'M') {
96 int month = 0;
97 if ((p = ParseInt(p + 1, 1, 12, &month)) != nullptr && *p == '.') {
98 int week = 0;
99 if ((p = ParseInt(p + 1, 1, 5, &week)) != nullptr && *p == '.') {
100 int weekday = 0;
101 if ((p = ParseInt(p + 1, 0, 6, &weekday)) != nullptr) {
102 res->date.fmt = PosixTransition::M;
103 res->date.m.month = static_cast<int_fast8_t>(month);
104 res->date.m.week = static_cast<int_fast8_t>(week);
105 res->date.m.weekday = static_cast<int_fast8_t>(weekday);
106 }
107 }
108 }
109 } else if (*p == 'J') {
110 int day = 0;
111 if ((p = ParseInt(p + 1, 1, 365, &day)) != nullptr) {
112 res->date.fmt = PosixTransition::J;
113 res->date.j.day = static_cast<int_fast16_t>(day);
114 }
115 } else {
116 int day = 0;
117 if ((p = ParseInt(p, 0, 365, &day)) != nullptr) {
118 res->date.fmt = PosixTransition::N;
119 res->date.j.day = static_cast<int_fast16_t>(day);
120 }
121 }
122 }
123 if (p != nullptr) {
124 res->time.offset = 2 * 60 * 60; // default offset is 02:00:00
125 if (*p == '/') p = ParseOffset(p + 1, -167, 167, 1, &res->time.offset);
126 }
127 return p;
128}
129
130} // namespace
131
132// spec = std offset [ dst [ offset ] , datetime , datetime ]
133bool ParsePosixSpec(const std::string& spec, PosixTimeZone* res) {
134 const char* p = spec.c_str();
135 if (*p == ':') return false;
136
137 p = ParseAbbr(p, &res->std_abbr);
138 p = ParseOffset(p, 0, 24, -1, &res->std_offset);
139 if (p == nullptr) return false;
140 if (*p == '\0') return true;
141
142 p = ParseAbbr(p, &res->dst_abbr);
143 if (p == nullptr) return false;
144 res->dst_offset = res->std_offset + (60 * 60); // default
145 if (*p != ',') p = ParseOffset(p, 0, 24, -1, &res->dst_offset);
146
147 p = ParseDateTime(p, &res->dst_start);
148 p = ParseDateTime(p, &res->dst_end);
149
150 return p != nullptr && *p == '\0';
151}
152
153} // namespace cctz
154} // namespace time_internal
155} // namespace absl
156