/
icu_timestamptz.c
224 lines (197 loc) · 5.69 KB
/
icu_timestamptz.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/*
* icu_timestamptz.c
*
* Part of icu_ext: a PostgreSQL extension to expose functionality from ICU
* (see http://icu-project.org)
*
* By Daniel Vérité, 2018-2023. See LICENSE.md
*/
#include "icu_ext.h"
/* Postgres includes */
#include "funcapi.h"
#include "pgtime.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
#include "utils/pg_locale.h"
#include "utils/date.h"
#include "utils/datetime.h"
/* ICU includes */
#include "unicode/ucal.h"
#include "unicode/udat.h"
#include "unicode/ustring.h"
PG_FUNCTION_INFO_V1(icu_timestamptz_in);
PG_FUNCTION_INFO_V1(icu_timestamptz_out);
PG_FUNCTION_INFO_V1(icu_date_to_ts);
PG_FUNCTION_INFO_V1(icu_ts_to_date);
/*
* icu_timestamptz_out()
* Convert a timestamp to external form.
*/
Datum
icu_timestamptz_out(PG_FUNCTION_ARGS)
{
TimestampTz dt = PG_GETARG_TIMESTAMPTZ(0);
char *result;
int tz;
struct pg_tm tt,
*tm = &tt;
fsec_t fsec;
const char *tzn;
char buf[MAXDATELEN + 1];
if (TIMESTAMP_NOT_FINITE(dt)) {
EncodeSpecialTimestamp(dt, buf);
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
}
else if (timestamp2tm(dt, &tz, tm, &fsec, &tzn, NULL) == 0)
{
UErrorCode status = U_ZERO_ERROR;
UDateFormat* df = NULL;
UDate udate = TS_TO_UDATE(dt);
const char *locale = NULL;
UChar *output_pattern = NULL;
int32_t pattern_length = -1;
UChar* tzid;
int32_t tzid_length;
UDateFormatStyle style = icu_ext_timestamptz_style;
const char *pg_tz_name = pg_get_timezone_name(session_timezone);
if (icu_ext_timestamptz_format != NULL)
{
if (icu_ext_timestamptz_format[0] != '\0' && icu_ext_timestamptz_style == UDAT_NONE)
{
pattern_length = icu_to_uchar(&output_pattern,
icu_ext_timestamptz_format,
strlen(icu_ext_timestamptz_format));
}
}
if (icu_ext_default_locale != NULL && icu_ext_default_locale[0] != '\0')
{
locale = icu_ext_default_locale;
}
/* use PG current timezone, hopefully compatible with ICU */
tzid_length = icu_to_uchar(&tzid,
pg_tz_name,
strlen(pg_tz_name));
/* if UDAT_PATTERN is passed, it must for both timeStyle and dateStyle */
df = udat_open(output_pattern ? UDAT_PATTERN : style, /* timeStyle */
output_pattern ? UDAT_PATTERN : style, /* dateStyle */
locale, /* NULL for the default locale */
tzid, /* tzID (NULL=default). */
tzid_length, /* tzIDLength */
output_pattern, /* pattern */
pattern_length, /* patternLength */
&status);
if (U_FAILURE(status))
elog(ERROR, "udat_open failed with code %d\n", status);
{
/* Try first to convert into a buffer on the stack, and
palloc() it only if udat_format says it's too small */
UChar local_buf[MAXDATELEN];
int32_t u_buffer_size = udat_format(df, udate,
local_buf, sizeof(local_buf)/sizeof(UChar),
NULL, &status);
if(status == U_BUFFER_OVERFLOW_ERROR)
{
UChar* u_buffer;
status = U_ZERO_ERROR;
u_buffer = (UChar*) palloc(u_buffer_size*sizeof(UChar));
udat_format(df, udate, u_buffer, u_buffer_size, NULL, &status);
icu_from_uchar(&result, u_buffer, u_buffer_size);
}
else
{
icu_from_uchar(&result, local_buf, u_buffer_size);
}
}
if (df)
udat_close(df);
PG_RETURN_CSTRING(result);
}
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
}
/*
* icu_timestamptz_in()
* Convert a string to internal form.
*/
Datum
icu_timestamptz_in(PG_FUNCTION_ARGS)
{
char *input_string = PG_GETARG_CSTRING(0);
int32_t pattern_length = -1;
UChar *u_ts_string;
int32_t u_ts_length;
UDateFormat* df = NULL;
UDate udat;
UDateFormatStyle style = icu_ext_timestamptz_style;
UErrorCode status = U_ZERO_ERROR;
UChar *input_pattern = NULL;
const char *locale = NULL;
int32_t parse_pos = 0;
UChar* tzid;
int32_t tzid_length;
const char *pg_tz_name = pg_get_timezone_name(session_timezone);
if (icu_ext_timestamptz_format != NULL)
{
if (icu_ext_timestamptz_format[0] != '\0' && style == UDAT_NONE)
{
pattern_length = icu_to_uchar(&input_pattern,
icu_ext_timestamptz_format,
strlen(icu_ext_timestamptz_format));
}
}
u_ts_length = icu_to_uchar(&u_ts_string, input_string, strlen(input_string));
if (icu_ext_default_locale != NULL && icu_ext_default_locale[0] != '\0')
{
locale = icu_ext_default_locale;
}
/* use PG current timezone, hopefully compatible with ICU */
tzid_length = icu_to_uchar(&tzid,
pg_tz_name,
strlen(pg_tz_name));
/* if UDAT_PATTERN is used, we must pass it for both timeStyle and dateStyle */
df = udat_open(input_pattern ? UDAT_PATTERN : style, /* timeStyle */
input_pattern ? UDAT_PATTERN : style, /* dateStyle */
locale,
tzid, /* tzID */
tzid_length, /* tzIDLength */
input_pattern,
pattern_length,
&status);
if (U_FAILURE(status))
{
udat_close(df);
elog(ERROR, "udat_open failed: %s\n", u_errorName(status));
}
udat_setLenient(df, false); /* strict parsing */
udat = udat_parse(df,
u_ts_string,
u_ts_length,
&parse_pos,
&status);
udat_close(df);
if (U_FAILURE(status))
elog(ERROR, "udat_parse failed: %s\n", u_errorName(status));
PG_RETURN_TIMESTAMPTZ(UDATE_TO_TS(udat));
}
/*
* Conversions between icu_timestamptz and icu_date are exactly the
* same as with the PG types timestamptz/date, since they share the
* same internal representation.
*/
Datum
icu_date_to_ts(PG_FUNCTION_ARGS)
{
return DirectFunctionCall2(date_timestamptz,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1));
}
Datum
icu_ts_to_date(PG_FUNCTION_ARGS)
{
return DirectFunctionCall2(timestamptz_date,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1));
}