|
4 | 4 | * Copyright 2021, Tarantool AUTHORS, please see AUTHORS file.
|
5 | 5 | */
|
6 | 6 |
|
| 7 | +#include <math.h> |
7 | 8 | #include <assert.h>
|
8 | 9 | #include <limits.h>
|
9 | 10 | #include <string.h>
|
|
12 | 13 | #include <inttypes.h>
|
13 | 14 |
|
14 | 15 | #define DT_PARSE_ISO_TNT
|
| 16 | +#include "decimal.h" |
| 17 | +#include "msgpuck.h" |
15 | 18 | #include "c-dt/dt.h"
|
16 | 19 | #include "datetime.h"
|
17 | 20 | #include "trivia/util.h"
|
18 | 21 | #include "tzcode/tzcode.h"
|
19 | 22 | #include "tzcode/timezone.h"
|
| 23 | +#include "mp_extension_types.h" |
20 | 24 |
|
21 | 25 | #include "fiber.h"
|
22 | 26 |
|
@@ -861,3 +865,188 @@ interval_interval_add(struct interval *lhs, const struct interval *rhs)
|
861 | 865 | lhs->nsec += rhs->nsec;
|
862 | 866 | return interval_check_args(lhs);
|
863 | 867 | }
|
| 868 | + |
| 869 | +/** This structure contains information about the given date and time fields. */ |
| 870 | +struct dt_fields { |
| 871 | + /* Specified year. */ |
| 872 | + double year; |
| 873 | + /* Specified month. */ |
| 874 | + double month; |
| 875 | + /* Specified day. */ |
| 876 | + double day; |
| 877 | + /* Specified hour. */ |
| 878 | + double hour; |
| 879 | + /* Specified minute. */ |
| 880 | + double min; |
| 881 | + /* Specified second. */ |
| 882 | + double sec; |
| 883 | + /* Specified millisecond. */ |
| 884 | + double msec; |
| 885 | + /* Specified microsecond. */ |
| 886 | + double usec; |
| 887 | + /* Specified nanosecond. */ |
| 888 | + double nsec; |
| 889 | + /* Specified timestamp. */ |
| 890 | + double timestamp; |
| 891 | + /* Specified timezone offset. */ |
| 892 | + double tzoffset; |
| 893 | + /* Number of given fields among msec, usec and nsec. */ |
| 894 | + int count_usec; |
| 895 | + /* True, if any of year, month, day, hour, min or sec is specified. */ |
| 896 | + bool is_ymdhms; |
| 897 | + /* True, if timestamp is specified. */ |
| 898 | + bool is_ts; |
| 899 | +}; |
| 900 | + |
| 901 | +/** Parse msgpack value and convert it to double, if possible. */ |
| 902 | +static int |
| 903 | +get_double_from_mp(const char **data, double *value) |
| 904 | +{ |
| 905 | + switch (mp_typeof(**data)) { |
| 906 | + case MP_INT: |
| 907 | + *value = mp_decode_int(data); |
| 908 | + break; |
| 909 | + case MP_UINT: |
| 910 | + *value = mp_decode_uint(data); |
| 911 | + break; |
| 912 | + case MP_DOUBLE: |
| 913 | + *value = mp_decode_double(data); |
| 914 | + break; |
| 915 | + case MP_EXT: { |
| 916 | + int8_t type; |
| 917 | + uint32_t len = mp_decode_extl(data, &type); |
| 918 | + if (type != MP_DECIMAL) |
| 919 | + return -1; |
| 920 | + decimal_t dec; |
| 921 | + if (decimal_unpack(data, len, &dec) == NULL) |
| 922 | + return -1; |
| 923 | + *value = atof(decimal_str(&dec)); |
| 924 | + break; |
| 925 | + } |
| 926 | + default: |
| 927 | + return -1; |
| 928 | + } |
| 929 | + return 0; |
| 930 | +} |
| 931 | + |
| 932 | +/** Define field of DATETIME value from field of given MAP value.*/ |
| 933 | +static int |
| 934 | +map_field_to_dt_field(struct dt_fields *fields, const char **data) |
| 935 | +{ |
| 936 | + if (mp_typeof(**data) != MP_STR) { |
| 937 | + mp_next(data); |
| 938 | + mp_next(data); |
| 939 | + return 0; |
| 940 | + } |
| 941 | + uint32_t size; |
| 942 | + const char *str = mp_decode_str(data, &size); |
| 943 | + double *value; |
| 944 | + if (strncmp(str, "year", size) == 0) { |
| 945 | + value = &fields->year; |
| 946 | + fields->is_ymdhms = true; |
| 947 | + } else if (strncmp(str, "month", size) == 0) { |
| 948 | + value = &fields->month; |
| 949 | + fields->is_ymdhms = true; |
| 950 | + } else if (strncmp(str, "day", size) == 0) { |
| 951 | + value = &fields->day; |
| 952 | + fields->is_ymdhms = true; |
| 953 | + } else if (strncmp(str, "hour", size) == 0) { |
| 954 | + value = &fields->hour; |
| 955 | + fields->is_ymdhms = true; |
| 956 | + } else if (strncmp(str, "min", size) == 0) { |
| 957 | + value = &fields->min; |
| 958 | + fields->is_ymdhms = true; |
| 959 | + } else if (strncmp(str, "sec", size) == 0) { |
| 960 | + value = &fields->sec; |
| 961 | + fields->is_ymdhms = true; |
| 962 | + } else if (strncmp(str, "msec", size) == 0) { |
| 963 | + value = &fields->msec; |
| 964 | + ++fields->count_usec; |
| 965 | + } else if (strncmp(str, "usec", size) == 0) { |
| 966 | + value = &fields->usec; |
| 967 | + ++fields->count_usec; |
| 968 | + } else if (strncmp(str, "nsec", size) == 0) { |
| 969 | + value = &fields->nsec; |
| 970 | + ++fields->count_usec; |
| 971 | + } else if (strncmp(str, "timestamp", size) == 0) { |
| 972 | + value = &fields->timestamp; |
| 973 | + fields->is_ts = true; |
| 974 | + } else if (strncmp(str, "tzoffset", size) == 0) { |
| 975 | + value = &fields->tzoffset; |
| 976 | + } else { |
| 977 | + mp_next(data); |
| 978 | + return 0; |
| 979 | + } |
| 980 | + return get_double_from_mp(data, value); |
| 981 | +} |
| 982 | + |
| 983 | +/** Create a DATETIME value using fields of the DATETIME. */ |
| 984 | +static int |
| 985 | +datetime_from_fields(struct datetime *dt, const struct dt_fields *fields) |
| 986 | +{ |
| 987 | + if (fields->count_usec > 1) |
| 988 | + return -1; |
| 989 | + double nsec = fields->msec * 1000000 + fields->usec * 1000 + |
| 990 | + fields->nsec; |
| 991 | + if (nsec < 0 || nsec > 1000000000) |
| 992 | + return -1; |
| 993 | + if (fields->tzoffset < -720 || fields->tzoffset > 840) |
| 994 | + return -1; |
| 995 | + if (fields->timestamp < (double)INT32_MIN * SECS_PER_DAY || |
| 996 | + fields->timestamp > (double)INT32_MAX * SECS_PER_DAY) |
| 997 | + return -1; |
| 998 | + if (fields->is_ts) { |
| 999 | + if (fields->is_ymdhms) |
| 1000 | + return -1; |
| 1001 | + double timestamp = floor(fields->timestamp); |
| 1002 | + double frac = fields->timestamp - timestamp; |
| 1003 | + if (frac != 0) { |
| 1004 | + if (fields->count_usec > 0) |
| 1005 | + return -1; |
| 1006 | + nsec = frac * 1000000000; |
| 1007 | + } |
| 1008 | + dt->epoch = timestamp; |
| 1009 | + dt->nsec = nsec; |
| 1010 | + dt->tzoffset = fields->tzoffset; |
| 1011 | + dt->tzindex = 0; |
| 1012 | + return 0; |
| 1013 | + } |
| 1014 | + if (fields->year < MIN_DATE_YEAR || fields->year > MAX_DATE_YEAR) |
| 1015 | + return -1; |
| 1016 | + if (fields->month < 1 || fields->month > 12) |
| 1017 | + return -1; |
| 1018 | + if (fields->day < 1 || |
| 1019 | + fields->day > dt_days_in_month(fields->year, fields->month)) |
| 1020 | + return -1; |
| 1021 | + if (fields->hour < 0 || fields->hour > 23) |
| 1022 | + return -1; |
| 1023 | + if (fields->min < 0 || fields->min > 59) |
| 1024 | + return -1; |
| 1025 | + if (fields->sec < 0 || fields->sec > 60) |
| 1026 | + return -1; |
| 1027 | + double days = dt_from_ymd(fields->year, fields->month, fields->day) - |
| 1028 | + DT_EPOCH_1970_OFFSET; |
| 1029 | + dt->epoch = days * SECS_PER_DAY + fields->hour * 3600 + |
| 1030 | + fields->min * 60 + fields->sec; |
| 1031 | + dt->nsec = nsec; |
| 1032 | + dt->tzoffset = fields->tzoffset; |
| 1033 | + dt->tzindex = 0; |
| 1034 | + return 0; |
| 1035 | +} |
| 1036 | + |
| 1037 | +int |
| 1038 | +datetime_from_map(struct datetime *dt, const char *data) |
| 1039 | +{ |
| 1040 | + assert(mp_typeof(*data) == MP_MAP); |
| 1041 | + uint32_t len = mp_decode_map(&data); |
| 1042 | + struct dt_fields fields; |
| 1043 | + memset(&fields, 0, sizeof(fields)); |
| 1044 | + fields.year = 1970; |
| 1045 | + fields.month = 1; |
| 1046 | + fields.day = 1; |
| 1047 | + for (uint32_t i = 0; i < len; ++i) { |
| 1048 | + if (map_field_to_dt_field(&fields, &data) != 0) |
| 1049 | + return -1; |
| 1050 | + } |
| 1051 | + return datetime_from_fields(dt, &fields); |
| 1052 | +} |
0 commit comments