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 
22 #include "absl/base/config.h"
23 
24 namespace absl {
25 ABSL_NAMESPACE_BEGIN
26 namespace time_internal {
27 namespace cctz {
28 
29 namespace {
30 
31 const char kDigits[] = "0123456789";
32 
ParseInt(const char * p,int min,int max,int * vp)33 const char* ParseInt(const char* p, int min, int max, int* vp) {
34   int value = 0;
35   const char* op = p;
36   const int kMaxInt = std::numeric_limits<int>::max();
37   for (; const char* dp = strchr(kDigits, *p); ++p) {
38     int d = static_cast<int>(dp - kDigits);
39     if (d >= 10) break;  // '\0'
40     if (value > kMaxInt / 10) return nullptr;
41     value *= 10;
42     if (value > kMaxInt - d) return nullptr;
43     value += d;
44   }
45   if (p == op || value < min || value > max) return nullptr;
46   *vp = value;
47   return p;
48 }
49 
50 // abbr = <.*?> | [^-+,\d]{3,}
ParseAbbr(const char * p,std::string * abbr)51 const char* ParseAbbr(const char* p, std::string* abbr) {
52   const char* op = p;
53   if (*p == '<') {  // special zoneinfo <...> form
54     while (*++p != '>') {
55       if (*p == '\0') return nullptr;
56     }
57     abbr->assign(op + 1, static_cast<std::size_t>(p - op) - 1);
58     return ++p;
59   }
60   while (*p != '\0') {
61     if (strchr("-+,", *p)) break;
62     if (strchr(kDigits, *p)) break;
63     ++p;
64   }
65   if (p - op < 3) return nullptr;
66   abbr->assign(op, static_cast<std::size_t>(p - op));
67   return p;
68 }
69 
70 // offset = [+|-]hh[:mm[:ss]] (aggregated into single seconds value)
ParseOffset(const char * p,int min_hour,int max_hour,int sign,std::int_fast32_t * offset)71 const char* ParseOffset(const char* p, int min_hour, int max_hour, int sign,
72                         std::int_fast32_t* offset) {
73   if (p == nullptr) return nullptr;
74   if (*p == '+' || *p == '-') {
75     if (*p++ == '-') sign = -sign;
76   }
77   int hours = 0;
78   int minutes = 0;
79   int seconds = 0;
80 
81   p = ParseInt(p, min_hour, max_hour, &hours);
82   if (p == nullptr) return nullptr;
83   if (*p == ':') {
84     p = ParseInt(p + 1, 0, 59, &minutes);
85     if (p == nullptr) return nullptr;
86     if (*p == ':') {
87       p = ParseInt(p + 1, 0, 59, &seconds);
88       if (p == nullptr) return nullptr;
89     }
90   }
91   *offset = sign * ((((hours * 60) + minutes) * 60) + seconds);
92   return p;
93 }
94 
95 // datetime = ( Jn | n | Mm.w.d ) [ / offset ]
ParseDateTime(const char * p,PosixTransition * res)96 const char* ParseDateTime(const char* p, PosixTransition* res) {
97   if (p != nullptr && *p == ',') {
98     if (*++p == 'M') {
99       int month = 0;
100       if ((p = ParseInt(p + 1, 1, 12, &month)) != nullptr && *p == '.') {
101         int week = 0;
102         if ((p = ParseInt(p + 1, 1, 5, &week)) != nullptr && *p == '.') {
103           int weekday = 0;
104           if ((p = ParseInt(p + 1, 0, 6, &weekday)) != nullptr) {
105             res->date.fmt = PosixTransition::M;
106             res->date.m.month = static_cast<std::int_fast8_t>(month);
107             res->date.m.week = static_cast<std::int_fast8_t>(week);
108             res->date.m.weekday = static_cast<std::int_fast8_t>(weekday);
109           }
110         }
111       }
112     } else if (*p == 'J') {
113       int day = 0;
114       if ((p = ParseInt(p + 1, 1, 365, &day)) != nullptr) {
115         res->date.fmt = PosixTransition::J;
116         res->date.j.day = static_cast<std::int_fast16_t>(day);
117       }
118     } else {
119       int day = 0;
120       if ((p = ParseInt(p, 0, 365, &day)) != nullptr) {
121         res->date.fmt = PosixTransition::N;
122         res->date.n.day = static_cast<std::int_fast16_t>(day);
123       }
124     }
125   }
126   if (p != nullptr) {
127     res->time.offset = 2 * 60 * 60;  // default offset is 02:00:00
128     if (*p == '/') p = ParseOffset(p + 1, -167, 167, 1, &res->time.offset);
129   }
130   return p;
131 }
132 
133 }  // namespace
134 
135 // spec = std offset [ dst [ offset ] , datetime , datetime ]
ParsePosixSpec(const std::string & spec,PosixTimeZone * res)136 bool ParsePosixSpec(const std::string& spec, PosixTimeZone* res) {
137   const char* p = spec.c_str();
138   if (*p == ':') return false;
139 
140   p = ParseAbbr(p, &res->std_abbr);
141   p = ParseOffset(p, 0, 24, -1, &res->std_offset);
142   if (p == nullptr) return false;
143   if (*p == '\0') return true;
144 
145   p = ParseAbbr(p, &res->dst_abbr);
146   if (p == nullptr) return false;
147   res->dst_offset = res->std_offset + (60 * 60);  // default
148   if (*p != ',') p = ParseOffset(p, 0, 24, -1, &res->dst_offset);
149 
150   p = ParseDateTime(p, &res->dst_start);
151   p = ParseDateTime(p, &res->dst_end);
152 
153   return p != nullptr && *p == '\0';
154 }
155 
156 }  // namespace cctz
157 }  // namespace time_internal
158 ABSL_NAMESPACE_END
159 }  // namespace absl
160