1#include "duckdb/common/types/time.hpp"
2#include "duckdb/common/types/timestamp.hpp"
3
4#include "duckdb/common/string_util.hpp"
5#include "duckdb/common/exception.hpp"
6
7#include <iomanip>
8#include <cstring>
9#include <iostream>
10#include <sstream>
11#include <cctype>
12
13using namespace duckdb;
14using namespace std;
15
16// string format is hh:mm:ssZ
17// Z is optional
18// ISO 8601
19
20// Taken from MonetDB mtime.c
21#define DD_TIME(h, m, s, x) \
22 ((h) >= 0 && (h) < 24 && (m) >= 0 && (m) < 60 && (s) >= 0 && (s) <= 60 && (x) >= 0 && (x) < 1000)
23
24static dtime_t time_to_number(int hour, int min, int sec, int msec) {
25 if (!DD_TIME(hour, min, sec, msec)) {
26 throw Exception("Invalid time");
27 }
28 return (dtime_t)(((((hour * 60) + min) * 60) + sec) * 1000 + msec);
29}
30
31static void number_to_time(dtime_t n, int32_t &hour, int32_t &min, int32_t &sec, int32_t &msec) {
32 int h, m, s, ms;
33
34 h = n / 3600000;
35 n -= h * 3600000;
36 m = n / 60000;
37 n -= m * 60000;
38 s = n / 1000;
39 n -= s * 1000;
40 ms = n;
41
42 hour = h;
43 min = m;
44 sec = s;
45 msec = ms;
46}
47
48// TODO this is duplicated in date.cpp
49static bool ParseDoubleDigit2(const char *buf, idx_t &pos, int32_t &result) {
50 if (std::isdigit((unsigned char)buf[pos])) {
51 result = buf[pos++] - '0';
52 if (std::isdigit((unsigned char)buf[pos])) {
53 result = (buf[pos++] - '0') + result * 10;
54 }
55 return true;
56 }
57 return false;
58}
59
60static bool TryConvertTime(const char *buf, dtime_t &result, bool strict = false) {
61 int32_t hour = -1, min = -1, sec = -1, msec = -1;
62 idx_t pos = 0;
63 int sep;
64
65 // skip leading spaces
66 while (std::isspace((unsigned char)buf[pos])) {
67 pos++;
68 }
69
70 if (!std::isdigit((unsigned char)buf[pos])) {
71 return false;
72 }
73
74 if (!ParseDoubleDigit2(buf, pos, hour)) {
75 return false;
76 }
77 if (hour < 0 || hour > 24) {
78 return false;
79 }
80
81 // fetch the separator
82 sep = buf[pos++];
83 if (sep != ':') {
84 // invalid separator
85 return false;
86 }
87
88 if (!ParseDoubleDigit2(buf, pos, min)) {
89 return false;
90 }
91 if (min < 0 || min > 60) {
92 return false;
93 }
94
95 if (buf[pos++] != sep) {
96 return false;
97 }
98
99 if (!ParseDoubleDigit2(buf, pos, sec)) {
100 return false;
101 }
102 if (sec < 0 || sec > 60) {
103 return false;
104 }
105
106 msec = 0;
107 sep = buf[pos++];
108 if (sep == '.') { // we expect some milliseconds
109 uint8_t mult = 100;
110 for (; std::isdigit((unsigned char)buf[pos]) && mult > 0; pos++, mult /= 10) {
111 msec += (buf[pos] - '0') * mult;
112 }
113 }
114
115 // in strict mode, check remaining string for non-space characters
116 if (strict) {
117 // skip trailing spaces
118 while (std::isspace((unsigned char)buf[pos])) {
119 pos++;
120 }
121 // check position. if end was not reached, non-space chars remaining
122 if (pos < strlen(buf)) {
123 return false;
124 }
125 }
126
127 result = Time::FromTime(hour, min, sec, msec);
128 return true;
129}
130
131dtime_t Time::FromCString(const char *buf, bool strict) {
132 dtime_t result;
133 if (!TryConvertTime(buf, result, strict)) {
134 // last chance, check if we can parse as timestamp
135 if (strlen(buf) > 10 && !strict) {
136 return Timestamp::GetTime(Timestamp::FromString(buf));
137 }
138 throw ConversionException("time field value out of range: \"%s\", "
139 "expected format is ([YYY-MM-DD ]HH:MM:SS[.MS])",
140 buf);
141 }
142 return result;
143}
144
145dtime_t Time::FromString(string str, bool strict) {
146 return Time::FromCString(str.c_str(), strict);
147}
148
149string Time::ToString(dtime_t time) {
150 int32_t hour, min, sec, msec;
151 number_to_time(time, hour, min, sec, msec);
152
153 if (msec > 0) {
154 return StringUtil::Format("%02d:%02d:%02d.%03d", hour, min, sec, msec);
155 } else {
156 return StringUtil::Format("%02d:%02d:%02d", hour, min, sec);
157 }
158}
159
160string Time::Format(int32_t hour, int32_t minute, int32_t second, int32_t milisecond) {
161 return ToString(Time::FromTime(hour, minute, second, milisecond));
162}
163
164dtime_t Time::FromTime(int32_t hour, int32_t minute, int32_t second, int32_t milisecond) {
165 return time_to_number(hour, minute, second, milisecond);
166}
167
168bool Time::IsValidTime(int32_t hour, int32_t minute, int32_t second, int32_t milisecond) {
169 return DD_TIME(hour, minute, second, milisecond);
170}
171
172void Time::Convert(dtime_t time, int32_t &out_hour, int32_t &out_min, int32_t &out_sec, int32_t &out_msec) {
173 number_to_time(time, out_hour, out_min, out_sec, out_msec);
174}
175