|
20 | 20 | import time |
21 | 21 | import base64 |
22 | 22 | import threading |
23 | | -import re |
24 | | -from dataclasses import dataclass |
25 | 23 |
|
26 | 24 | from google.protobuf.struct_pb2 import ListValue |
27 | 25 | from google.protobuf.struct_pb2 import Value |
|
33 | 31 | from google.cloud._helpers import _date_from_iso8601_date |
34 | 32 | from google.cloud.spanner_v1 import TypeCode |
35 | 33 | from google.cloud.spanner_v1 import ExecuteSqlRequest |
36 | | -from google.cloud.spanner_v1 import JsonObject |
| 34 | +from google.cloud.spanner_v1 import JsonObject, Interval |
37 | 35 | from google.cloud.spanner_v1 import TransactionOptions |
38 | 36 | from google.cloud.spanner_v1.request_id_header import with_request_id |
39 | 37 | from google.rpc.error_details_pb2 import RetryInfo |
@@ -198,152 +196,6 @@ def _datetime_to_rfc3339_nanoseconds(value): |
198 | 196 | return "{}.{}Z".format(value.isoformat(sep="T", timespec="seconds"), nanos) |
199 | 197 |
|
200 | 198 |
|
201 | | -@dataclass |
202 | | -class Interval: |
203 | | - """Represents a Spanner INTERVAL type. |
204 | | -
|
205 | | - An interval is a combination of months, days and nanoseconds. |
206 | | - Internally, Spanner supports Interval value with the following range of individual fields: |
207 | | - months: [-120000, 120000] |
208 | | - days: [-3660000, 3660000] |
209 | | - nanoseconds: [-316224000000000000000, 316224000000000000000] |
210 | | - """ |
211 | | - |
212 | | - months: int = 0 |
213 | | - days: int = 0 |
214 | | - nanos: int = 0 |
215 | | - |
216 | | - def __str__(self) -> str: |
217 | | - """Returns the ISO8601 duration format string representation.""" |
218 | | - result = ["P"] |
219 | | - |
220 | | - # Handle years and months |
221 | | - if self.months: |
222 | | - is_negative = self.months < 0 |
223 | | - abs_months = abs(self.months) |
224 | | - years, months = divmod(abs_months, 12) |
225 | | - if years: |
226 | | - result.append(f"{'-' if is_negative else ''}{years}Y") |
227 | | - if months: |
228 | | - result.append(f"{'-' if is_negative else ''}{months}M") |
229 | | - |
230 | | - # Handle days |
231 | | - if self.days: |
232 | | - result.append(f"{self.days}D") |
233 | | - |
234 | | - # Handle time components |
235 | | - if self.nanos: |
236 | | - result.append("T") |
237 | | - nanos = abs(self.nanos) |
238 | | - is_negative = self.nanos < 0 |
239 | | - |
240 | | - # Convert to hours, minutes, seconds |
241 | | - nanos_per_hour = 3600000000000 |
242 | | - hours, nanos = divmod(nanos, nanos_per_hour) |
243 | | - if hours: |
244 | | - if is_negative: |
245 | | - result.append("-") |
246 | | - result.append(f"{hours}H") |
247 | | - |
248 | | - nanos_per_minute = 60000000000 |
249 | | - minutes, nanos = divmod(nanos, nanos_per_minute) |
250 | | - if minutes: |
251 | | - if is_negative: |
252 | | - result.append("-") |
253 | | - result.append(f"{minutes}M") |
254 | | - |
255 | | - nanos_per_second = 1000000000 |
256 | | - seconds, nanos_fraction = divmod(nanos, nanos_per_second) |
257 | | - |
258 | | - if seconds or nanos_fraction: |
259 | | - if is_negative: |
260 | | - result.append("-") |
261 | | - if seconds: |
262 | | - result.append(str(seconds)) |
263 | | - elif nanos_fraction: |
264 | | - result.append("0") |
265 | | - |
266 | | - if nanos_fraction: |
267 | | - nano_str = f"{nanos_fraction:09d}" |
268 | | - trimmed = nano_str.rstrip("0") |
269 | | - if len(trimmed) <= 3: |
270 | | - while len(trimmed) < 3: |
271 | | - trimmed += "0" |
272 | | - elif len(trimmed) <= 6: |
273 | | - while len(trimmed) < 6: |
274 | | - trimmed += "0" |
275 | | - else: |
276 | | - while len(trimmed) < 9: |
277 | | - trimmed += "0" |
278 | | - result.append(f".{trimmed}") |
279 | | - result.append("S") |
280 | | - |
281 | | - if len(result) == 1: |
282 | | - result.append("0Y") # Special case for zero interval |
283 | | - |
284 | | - return "".join(result) |
285 | | - |
286 | | - @classmethod |
287 | | - def from_str(cls, s: str) -> "Interval": |
288 | | - """Parse an ISO8601 duration format string into an Interval.""" |
289 | | - pattern = r"^P(-?\d+Y)?(-?\d+M)?(-?\d+D)?(T(-?\d+H)?(-?\d+M)?(-?((\d+([.,]\d{1,9})?)|([.,]\d{1,9}))S)?)?$" |
290 | | - match = re.match(pattern, s) |
291 | | - if not match or len(s) == 1: |
292 | | - raise ValueError(f"Invalid interval format: {s}") |
293 | | - |
294 | | - parts = match.groups() |
295 | | - if not any(parts[:3]) and not parts[3]: |
296 | | - raise ValueError( |
297 | | - f"Invalid interval format: at least one component (Y/M/D/H/M/S) is required: {s}" |
298 | | - ) |
299 | | - |
300 | | - if parts[3] == "T" and not any(parts[4:7]): |
301 | | - raise ValueError( |
302 | | - f"Invalid interval format: time designator 'T' present but no time components specified: {s}" |
303 | | - ) |
304 | | - |
305 | | - def parse_num(s: str, suffix: str) -> int: |
306 | | - if not s: |
307 | | - return 0 |
308 | | - return int(s.rstrip(suffix)) |
309 | | - |
310 | | - years = parse_num(parts[0], "Y") |
311 | | - months = parse_num(parts[1], "M") |
312 | | - total_months = years * 12 + months |
313 | | - |
314 | | - days = parse_num(parts[2], "D") |
315 | | - |
316 | | - nanos = 0 |
317 | | - if parts[3]: # Has time component |
318 | | - # Convert hours to nanoseconds |
319 | | - hours = parse_num(parts[4], "H") |
320 | | - nanos += hours * 3600000000000 |
321 | | - |
322 | | - # Convert minutes to nanoseconds |
323 | | - minutes = parse_num(parts[5], "M") |
324 | | - nanos += minutes * 60000000000 |
325 | | - |
326 | | - # Handle seconds and fractional seconds |
327 | | - if parts[6]: |
328 | | - seconds = parts[6].rstrip("S") |
329 | | - if "," in seconds: |
330 | | - seconds = seconds.replace(",", ".") |
331 | | - |
332 | | - if "." in seconds: |
333 | | - sec_parts = seconds.split(".") |
334 | | - whole_seconds = sec_parts[0] if sec_parts[0] else "0" |
335 | | - nanos += int(whole_seconds) * 1000000000 |
336 | | - frac = sec_parts[1][:9].ljust(9, "0") |
337 | | - frac_nanos = int(frac) |
338 | | - if seconds.startswith("-"): |
339 | | - frac_nanos = -frac_nanos |
340 | | - nanos += frac_nanos |
341 | | - else: |
342 | | - nanos += int(seconds) * 1000000000 |
343 | | - |
344 | | - return cls(months=total_months, days=days, nanos=nanos) |
345 | | - |
346 | | - |
347 | 199 | def _make_value_pb(value): |
348 | 200 | """Helper for :func:`_make_list_value_pbs`. |
349 | 201 |
|
|
0 commit comments