1/*
2 Copyright (c) 2004, 2012, Oracle and/or its affiliates.
3 Copyright (c) 2013, MariaDB Foundation.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
17
18#include "mariadb.h"
19#include "compat56.h"
20#include "myisampack.h"
21#include "my_time.h"
22
23/*** MySQL56 TIME low-level memory and disk representation routines ***/
24
25/*
26 In-memory format:
27
28 1 bit sign (Used for sign, when on disk)
29 1 bit unused (Reserved for wider hour range, e.g. for intervals)
30 10 bit hour (0-836)
31 6 bit minute (0-59)
32 6 bit second (0-59)
33 24 bits microseconds (0-999999)
34
35 Total: 48 bits = 6 bytes
36 Suhhhhhh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
37*/
38
39
40/**
41 Convert time value to MySQL56 numeric packed representation.
42
43 @param ltime The value to convert.
44 @return Numeric packed representation.
45*/
46longlong TIME_to_longlong_time_packed(const MYSQL_TIME *ltime)
47{
48 DBUG_ASSERT(ltime->year == 0);
49 DBUG_ASSERT(ltime->month == 0);
50 // Mix days with hours: "1 00:10:10" -> "24:10:10"
51 long hms= ((ltime->day * 24 + ltime->hour) << 12) |
52 (ltime->minute << 6) | ltime->second;
53 longlong tmp= MY_PACKED_TIME_MAKE(hms, ltime->second_part);
54 return ltime->neg ? -tmp : tmp;
55}
56
57
58
59/**
60 Convert MySQL56 time packed numeric representation to time.
61
62 @param OUT ltime The MYSQL_TIME variable to set.
63 @param tmp The packed numeric representation.
64*/
65void TIME_from_longlong_time_packed(MYSQL_TIME *ltime, longlong tmp)
66{
67 long hms;
68 if ((ltime->neg= (tmp < 0)))
69 tmp= -tmp;
70 hms= (long) MY_PACKED_TIME_GET_INT_PART(tmp);
71 ltime->year= (uint) 0;
72 ltime->month= (uint) 0;
73 ltime->day= (uint) 0;
74 ltime->hour= (uint) (hms >> 12) % (1 << 10); /* 10 bits starting at 12th */
75 ltime->minute= (uint) (hms >> 6) % (1 << 6); /* 6 bits starting at 6th */
76 ltime->second= (uint) hms % (1 << 6); /* 6 bits starting at 0th */
77 ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp);
78 ltime->time_type= MYSQL_TIMESTAMP_TIME;
79}
80
81
82/**
83 Calculate binary size of MySQL56 packed numeric time representation.
84
85 @param dec Precision.
86*/
87uint my_time_binary_length(uint dec)
88{
89 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
90 return 3 + (dec + 1) / 2;
91}
92
93
94/*
95 On disk we convert from signed representation to unsigned
96 representation using TIMEF_OFS, so all values become binary comparable.
97*/
98#define TIMEF_OFS 0x800000000000LL
99#define TIMEF_INT_OFS 0x800000LL
100
101
102/**
103 Convert MySQL56 in-memory numeric time representation to on-disk representation
104
105 @param nr Value in packed numeric time format.
106 @param OUT ptr The buffer to put value at.
107 @param dec Precision.
108*/
109void my_time_packed_to_binary(longlong nr, uchar *ptr, uint dec)
110{
111 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
112 /* Make sure the stored value was previously properly rounded or truncated */
113 DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) %
114 (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
115
116 switch (dec)
117 {
118 case 0:
119 default:
120 mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
121 break;
122
123 case 1:
124 case 2:
125 mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
126 ptr[3]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000);
127 break;
128
129 case 4:
130 case 3:
131 mi_int3store(ptr, TIMEF_INT_OFS + MY_PACKED_TIME_GET_INT_PART(nr));
132 mi_int2store(ptr + 3, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100);
133 break;
134
135 case 5:
136 case 6:
137 mi_int6store(ptr, nr + TIMEF_OFS);
138 break;
139 }
140}
141
142
143/**
144 Convert MySQL56 on-disk time representation to in-memory packed numeric
145 representation.
146
147 @param ptr The pointer to read the value at.
148 @param dec Precision.
149 @return Packed numeric time representation.
150*/
151longlong my_time_packed_from_binary(const uchar *ptr, uint dec)
152{
153 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
154
155 switch (dec)
156 {
157 case 0:
158 default:
159 {
160 longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
161 return MY_PACKED_TIME_MAKE_INT(intpart);
162 }
163 case 1:
164 case 2:
165 {
166 longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
167 int frac= (uint) ptr[3];
168 if (intpart < 0 && frac)
169 {
170 /*
171 Negative values are stored with reverse fractional part order,
172 for binary sort compatibility.
173
174 Disk value intpart frac Time value Memory value
175 800000.00 0 0 00:00:00.00 0000000000.000000
176 7FFFFF.FF -1 255 -00:00:00.01 FFFFFFFFFF.FFD8F0
177 7FFFFF.9D -1 99 -00:00:00.99 FFFFFFFFFF.F0E4D0
178 7FFFFF.00 -1 0 -00:00:01.00 FFFFFFFFFF.000000
179 7FFFFE.FF -1 255 -00:00:01.01 FFFFFFFFFE.FFD8F0
180 7FFFFE.F6 -2 246 -00:00:01.10 FFFFFFFFFE.FE7960
181
182 Formula to convert fractional part from disk format
183 (now stored in "frac" variable) to absolute value: "0x100 - frac".
184 To reconstruct in-memory value, we shift
185 to the next integer value and then substruct fractional part.
186 */
187 intpart++; /* Shift to the next integer value */
188 frac-= 0x100; /* -(0x100 - frac) */
189 }
190 return MY_PACKED_TIME_MAKE(intpart, frac * 10000);
191 }
192
193 case 3:
194 case 4:
195 {
196 longlong intpart= mi_uint3korr(ptr) - TIMEF_INT_OFS;
197 int frac= mi_uint2korr(ptr + 3);
198 if (intpart < 0 && frac)
199 {
200 /*
201 Fix reverse fractional part order: "0x10000 - frac".
202 See comments for FSP=1 and FSP=2 above.
203 */
204 intpart++; /* Shift to the next integer value */
205 frac-= 0x10000; /* -(0x10000-frac) */
206 }
207 return MY_PACKED_TIME_MAKE(intpart, frac * 100);
208 }
209
210 case 5:
211 case 6:
212 return ((longlong) mi_uint6korr(ptr)) - TIMEF_OFS;
213 }
214}
215
216
217/*** MySQL56 DATETIME low-level memory and disk representation routines ***/
218
219/*
220 1 bit sign (used when on disk)
221 17 bits year*13+month (year 0-9999, month 0-12)
222 5 bits day (0-31)
223 5 bits hour (0-23)
224 6 bits minute (0-59)
225 6 bits second (0-59)
226 24 bits microseconds (0-999999)
227
228 Total: 64 bits = 8 bytes
229
230 SYYYYYYY.YYYYYYYY.YYdddddh.hhhhmmmm.mmssssss.ffffffff.ffffffff.ffffffff
231*/
232
233/**
234 Convert datetime to MySQL56 packed numeric datetime representation.
235 @param ltime The value to convert.
236 @return Packed numeric representation of ltime.
237*/
238longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME *ltime)
239{
240 longlong ymd= ((ltime->year * 13 + ltime->month) << 5) | ltime->day;
241 longlong hms= (ltime->hour << 12) | (ltime->minute << 6) | ltime->second;
242 longlong tmp= MY_PACKED_TIME_MAKE(((ymd << 17) | hms), ltime->second_part);
243 DBUG_ASSERT(!check_datetime_range(ltime)); /* Make sure no overflow */
244 return ltime->neg ? -tmp : tmp;
245}
246
247
248/**
249 Convert MySQL56 packed numeric datetime representation to MYSQL_TIME.
250 @param OUT ltime The datetime variable to convert to.
251 @param tmp The packed numeric datetime value.
252*/
253void TIME_from_longlong_datetime_packed(MYSQL_TIME *ltime, longlong tmp)
254{
255 longlong ymd, hms;
256 longlong ymdhms, ym;
257 if ((ltime->neg= (tmp < 0)))
258 tmp= -tmp;
259
260 ltime->second_part= MY_PACKED_TIME_GET_FRAC_PART(tmp);
261 ymdhms= MY_PACKED_TIME_GET_INT_PART(tmp);
262
263 ymd= ymdhms >> 17;
264 ym= ymd >> 5;
265 hms= ymdhms % (1 << 17);
266
267 ltime->day= ymd % (1 << 5);
268 ltime->month= ym % 13;
269 ltime->year= (uint) (ym / 13);
270
271 ltime->second= hms % (1 << 6);
272 ltime->minute= (hms >> 6) % (1 << 6);
273 ltime->hour= (uint) (hms >> 12);
274
275 ltime->time_type= MYSQL_TIMESTAMP_DATETIME;
276}
277
278
279/**
280 Calculate binary size of MySQL56 packed datetime representation.
281 @param dec Precision.
282*/
283uint my_datetime_binary_length(uint dec)
284{
285 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
286 return 5 + (dec + 1) / 2;
287}
288
289
290/*
291 On disk we store as unsigned number with DATETIMEF_INT_OFS offset,
292 for HA_KETYPE_BINARY compatibilty purposes.
293*/
294#define DATETIMEF_INT_OFS 0x8000000000LL
295
296
297/**
298 Convert MySQL56 on-disk datetime representation
299 to in-memory packed numeric representation.
300
301 @param ptr The pointer to read value at.
302 @param dec Precision.
303 @return In-memory packed numeric datetime representation.
304*/
305longlong my_datetime_packed_from_binary(const uchar *ptr, uint dec)
306{
307 longlong intpart= mi_uint5korr(ptr) - DATETIMEF_INT_OFS;
308 int frac;
309 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
310 switch (dec)
311 {
312 case 0:
313 default:
314 return MY_PACKED_TIME_MAKE_INT(intpart);
315 case 1:
316 case 2:
317 frac= ((int) (signed char) ptr[5]) * 10000;
318 break;
319 case 3:
320 case 4:
321 frac= mi_sint2korr(ptr + 5) * 100;
322 break;
323 case 5:
324 case 6:
325 frac= mi_sint3korr(ptr + 5);
326 break;
327 }
328 return MY_PACKED_TIME_MAKE(intpart, frac);
329}
330
331
332/**
333 Store MySQL56 in-memory numeric packed datetime representation to disk.
334
335 @param nr In-memory numeric packed datetime representation.
336 @param OUT ptr The pointer to store at.
337 @param dec Precision, 1-6.
338*/
339void my_datetime_packed_to_binary(longlong nr, uchar *ptr, uint dec)
340{
341 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
342 /* The value being stored must have been properly rounded or truncated */
343 DBUG_ASSERT((MY_PACKED_TIME_GET_FRAC_PART(nr) %
344 (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
345
346 mi_int5store(ptr, MY_PACKED_TIME_GET_INT_PART(nr) + DATETIMEF_INT_OFS);
347 switch (dec)
348 {
349 case 0:
350 default:
351 break;
352 case 1:
353 case 2:
354 ptr[5]= (unsigned char) (char) (MY_PACKED_TIME_GET_FRAC_PART(nr) / 10000);
355 break;
356 case 3:
357 case 4:
358 mi_int2store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr) / 100);
359 break;
360 case 5:
361 case 6:
362 mi_int3store(ptr + 5, MY_PACKED_TIME_GET_FRAC_PART(nr));
363 }
364}
365
366
367/*** MySQL56 TIMESTAMP low-level memory and disk representation routines ***/
368
369/**
370 Calculate on-disk size of a timestamp value.
371
372 @param dec Precision.
373*/
374uint my_timestamp_binary_length(uint dec)
375{
376 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
377 return 4 + (dec + 1) / 2;
378}
379
380
381/**
382 Convert MySQL56 binary timestamp representation to in-memory representation.
383
384 @param OUT tm The variable to convert to.
385 @param ptr The pointer to read the value from.
386 @param dec Precision.
387*/
388void my_timestamp_from_binary(struct timeval *tm, const uchar *ptr, uint dec)
389{
390 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
391 tm->tv_sec= mi_uint4korr(ptr);
392 switch (dec)
393 {
394 case 0:
395 default:
396 tm->tv_usec= 0;
397 break;
398 case 1:
399 case 2:
400 tm->tv_usec= ((int) ptr[4]) * 10000;
401 break;
402 case 3:
403 case 4:
404 tm->tv_usec= mi_sint2korr(ptr + 4) * 100;
405 break;
406 case 5:
407 case 6:
408 tm->tv_usec= mi_sint3korr(ptr + 4);
409 }
410}
411
412
413/**
414 Convert MySQL56 in-memory timestamp representation to on-disk representation.
415
416 @param tm The value to convert.
417 @param OUT ptr The pointer to store the value to.
418 @param dec Precision.
419*/
420void my_timestamp_to_binary(const struct timeval *tm, uchar *ptr, uint dec)
421{
422 DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS);
423 /* Stored value must have been previously properly rounded or truncated */
424 DBUG_ASSERT((tm->tv_usec %
425 (int) log_10_int[TIME_SECOND_PART_DIGITS - dec]) == 0);
426 mi_int4store(ptr, tm->tv_sec);
427 switch (dec)
428 {
429 case 0:
430 default:
431 break;
432 case 1:
433 case 2:
434 ptr[4]= (unsigned char) (char) (tm->tv_usec / 10000);
435 break;
436 case 3:
437 case 4:
438 mi_int2store(ptr + 4, tm->tv_usec / 100);
439 break;
440 /* Impossible second precision. Fall through */
441 case 5:
442 case 6:
443 mi_int3store(ptr + 4, tm->tv_usec);
444 }
445}
446
447/****************************************/
448