mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-22 21:23:04 +01:00
Internal support for time zone regions in addition to offsets.
Simulate regions with fixed BRT and BRST regions.
This commit is contained in:
parent
4812ce8ee8
commit
8792dff315
@ -33,14 +33,37 @@ using namespace Firebird;
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
struct TimeZoneDesc
|
||||
{
|
||||
USHORT id;
|
||||
const char* abbr;
|
||||
};
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
static SSHORT getDisplacement(const ISC_TIMESTAMP_TZ& timeStampTz);
|
||||
static SSHORT getDisplacement(const ISC_TIMESTAMP& timeStampUtc, USHORT timeZone);
|
||||
static inline bool isOffset(USHORT timeZone);
|
||||
static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm);
|
||||
static USHORT makeFromRegion(const char* str, unsigned strLen);
|
||||
static inline SSHORT offsetZoneToDisplacement(USHORT timeZone);
|
||||
static int parseNumber(const char*& p, const char* end);
|
||||
static void skipSpaces(const char*& p, const char* end);
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
static const TimeZoneDesc TIME_ZONE_LIST[] = { //// FIXME:
|
||||
{0, "BRT"},
|
||||
{1, "BRST"}
|
||||
};
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
//// FIXME: Windows and others ports.
|
||||
// Return the current user's time zone.
|
||||
USHORT TimeZoneUtil::getCurrent()
|
||||
{
|
||||
//// FIXME: Return the time zone region instead of the offset.
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
|
||||
@ -48,9 +71,14 @@ USHORT TimeZoneUtil::getCurrent()
|
||||
if (!localtime_r(&rawtime, &tm1))
|
||||
system_call_failed::raise("localtime_r");
|
||||
|
||||
return (USHORT) SSHORT(tm1.tm_gmtoff / 60);
|
||||
int sign = tm1.tm_gmtoff < 0 ? -1 : 1;
|
||||
unsigned tzh = (unsigned) abs(int(tm1.tm_gmtoff / 60 / 60));
|
||||
unsigned tzm = (unsigned) abs(int(tm1.tm_gmtoff / 60 % 60));
|
||||
|
||||
return makeFromOffset(sign, tzh, tzm);
|
||||
}
|
||||
|
||||
// Parses a time zone, offset- or region-based.
|
||||
USHORT TimeZoneUtil::parse(const char* str, unsigned strLen)
|
||||
{
|
||||
const char* end = str + strLen;
|
||||
@ -59,97 +87,137 @@ USHORT TimeZoneUtil::parse(const char* str, unsigned strLen)
|
||||
skipSpaces(p, end);
|
||||
|
||||
int sign = 1;
|
||||
bool signPresent = false;
|
||||
|
||||
if (*p == '-' || *p == '+')
|
||||
{
|
||||
signPresent = true;
|
||||
sign = *p == '-' ? -1 : 1;
|
||||
++p;
|
||||
skipSpaces(p, end);
|
||||
}
|
||||
|
||||
int tzh = parseNumber(p, end);
|
||||
int tzm = 0;
|
||||
|
||||
skipSpaces(p, end);
|
||||
|
||||
if (*p == ':')
|
||||
if (signPresent || (*p >= '0' && *p <= '9'))
|
||||
{
|
||||
++p;
|
||||
skipSpaces(p, end);
|
||||
tzm = (unsigned) parseNumber(p, end);
|
||||
int tzh = parseNumber(p, end);
|
||||
int tzm = 0;
|
||||
|
||||
skipSpaces(p, end);
|
||||
|
||||
if (*p == ':')
|
||||
{
|
||||
++p;
|
||||
skipSpaces(p, end);
|
||||
tzm = (unsigned) parseNumber(p, end);
|
||||
skipSpaces(p, end);
|
||||
}
|
||||
|
||||
if (p != end)
|
||||
status_exception::raise(Arg::Gds(isc_random) << "Invalid time zone offset"); //// TODO:
|
||||
|
||||
return makeFromOffset(sign, tzh, tzm);
|
||||
}
|
||||
|
||||
if (p != end)
|
||||
status_exception::raise(Arg::Gds(isc_random) << "Invalid time zone offset"); //// TODO:
|
||||
|
||||
if (!isValidOffset(sign, tzh, tzm))
|
||||
status_exception::raise(Arg::Gds(isc_random) << "Invalid time zone offset"); //// TODO:
|
||||
|
||||
return (USHORT)(SSHORT) (tzh * 60 + tzm) * sign;
|
||||
else
|
||||
return makeFromRegion(p, str + strLen - p);
|
||||
}
|
||||
|
||||
unsigned TimeZoneUtil::format(char* buffer, USHORT zone)
|
||||
// Format a time zone to string, as offset or region.
|
||||
unsigned TimeZoneUtil::format(char* buffer, size_t bufferSize, USHORT timeZone)
|
||||
{
|
||||
char* p = buffer;
|
||||
|
||||
SSHORT displacement = (SSHORT) zone;
|
||||
if (isOffset(timeZone))
|
||||
{
|
||||
SSHORT displacement = offsetZoneToDisplacement(timeZone);
|
||||
|
||||
*p++ = displacement < 0 ? '-' : '+';
|
||||
*p++ = displacement < 0 ? '-' : '+';
|
||||
|
||||
if (displacement < 0)
|
||||
displacement = -displacement;
|
||||
if (displacement < 0)
|
||||
displacement = -displacement;
|
||||
|
||||
sprintf(p, "%2.2d:%2.2d", displacement / 60, displacement % 60);
|
||||
p += fb_utils::snprintf(p, bufferSize - 1, "%2.2d:%2.2d", displacement / 60, displacement % 60);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MAX_USHORT - timeZone < FB_NELEM(TIME_ZONE_LIST))
|
||||
strncpy(buffer, TIME_ZONE_LIST[MAX_USHORT - timeZone].abbr, bufferSize);
|
||||
else
|
||||
{
|
||||
fb_assert(false);
|
||||
strncpy(buffer, "*Invalid*", bufferSize);
|
||||
}
|
||||
|
||||
while (*p)
|
||||
p++;
|
||||
p += strlen(buffer);
|
||||
}
|
||||
|
||||
return p - buffer;
|
||||
}
|
||||
|
||||
bool TimeZoneUtil::isValidOffset(int sign, int tzh, unsigned tzm)
|
||||
// Returns if the offsets are valid.
|
||||
bool TimeZoneUtil::isValidOffset(int sign, unsigned tzh, unsigned tzm)
|
||||
{
|
||||
fb_assert(sign >= -1 && sign <= 1);
|
||||
return tzm <= 59 && (tzh < 14 || (tzh == 14 && tzm == 0));
|
||||
}
|
||||
|
||||
// Extracts the offsets from a offset- or region-based datetime with time zone.
|
||||
void TimeZoneUtil::extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, int* sign, unsigned* tzh, unsigned* tzm)
|
||||
{
|
||||
SSHORT offset = (SSHORT) timeStampTz.timestamp_zone;
|
||||
SSHORT displacement;
|
||||
|
||||
*sign = offset < 0 ? -1 : 1;
|
||||
offset = offset < 0 ? -offset : offset;
|
||||
if (isOffset(timeStampTz.timestamp_zone))
|
||||
displacement = offsetZoneToDisplacement(timeStampTz.timestamp_zone);
|
||||
else
|
||||
{
|
||||
ISC_TIMESTAMP ts1 = *(ISC_TIMESTAMP*) &timeStampTz;
|
||||
ISC_TIMESTAMP ts2 = timeStampTzAtZone(timeStampTz, UTC_ZONE);
|
||||
|
||||
*tzh = offset / 60;
|
||||
*tzm = offset % 60;
|
||||
displacement =
|
||||
((ts1.timestamp_date * TimeStamp::ISC_TICKS_PER_DAY + ts1.timestamp_time) -
|
||||
(ts2.timestamp_date * TimeStamp::ISC_TICKS_PER_DAY + ts2.timestamp_time)) /
|
||||
(ISC_TIME_SECONDS_PRECISION * 60);
|
||||
}
|
||||
|
||||
*sign = displacement < 0 ? -1 : 1;
|
||||
displacement = displacement < 0 ? -displacement : displacement;
|
||||
|
||||
*tzh = displacement / 60;
|
||||
*tzm = displacement % 60;
|
||||
}
|
||||
|
||||
ISC_TIME TimeZoneUtil::timeTzAtZone(const ISC_TIME_TZ& timeTz, USHORT zone)
|
||||
// Moves a time from one time zone to another.
|
||||
ISC_TIME TimeZoneUtil::timeTzAtZone(const ISC_TIME_TZ& timeTz, USHORT atTimeZone)
|
||||
{
|
||||
SSHORT zoneDisplacement = (SSHORT) zone;
|
||||
ISC_TIMESTAMP_TZ tempTimeStampTz;
|
||||
tempTimeStampTz.timestamp_date = TimeStamp::getCurrentTimeStamp().value().timestamp_date;
|
||||
tempTimeStampTz.timestamp_time = timeTz.time_time;
|
||||
tempTimeStampTz.timestamp_zone = timeTz.time_zone;
|
||||
|
||||
SLONG ticks = timeTz.time_time -
|
||||
((SSHORT) timeTz.time_zone - zoneDisplacement) * 60 * ISC_TIME_SECONDS_PRECISION;
|
||||
|
||||
// Make the result positive
|
||||
while (ticks < 0)
|
||||
ticks += TimeStamp::ISC_TICKS_PER_DAY;
|
||||
|
||||
// And make it in the range of values for a day
|
||||
ticks %= TimeStamp::ISC_TICKS_PER_DAY;
|
||||
|
||||
fb_assert(ticks >= 0 && ticks < TimeStamp::ISC_TICKS_PER_DAY);
|
||||
|
||||
return (ISC_TIME) ticks;
|
||||
return timeStampTzAtZone(tempTimeStampTz, atTimeZone).timestamp_time;
|
||||
}
|
||||
|
||||
ISC_TIMESTAMP TimeZoneUtil::timeStampTzAtZone(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT zone)
|
||||
// Moves a timestamp from one time zone to another.
|
||||
ISC_TIMESTAMP TimeZoneUtil::timeStampTzAtZone(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT atTimeZone)
|
||||
{
|
||||
SSHORT zoneDisplacement = (SSHORT) zone;
|
||||
SSHORT timeDisplacement = getDisplacement(timeStampTz);
|
||||
|
||||
SINT64 ticks = timeStampTz.timestamp_date * TimeStamp::ISC_TICKS_PER_DAY + timeStampTz.timestamp_time -
|
||||
((SSHORT) timeStampTz.timestamp_zone - zoneDisplacement) * 60 * ISC_TIME_SECONDS_PRECISION;
|
||||
(timeDisplacement * 60 * ISC_TIME_SECONDS_PRECISION);
|
||||
|
||||
SSHORT atDisplacement;
|
||||
|
||||
if (isOffset(atTimeZone))
|
||||
atDisplacement = offsetZoneToDisplacement(atTimeZone);
|
||||
else
|
||||
{
|
||||
ISC_TIMESTAMP tempTimeStampUtc;
|
||||
tempTimeStampUtc.timestamp_date = ticks / TimeStamp::ISC_TICKS_PER_DAY;
|
||||
tempTimeStampUtc.timestamp_time = ticks % TimeStamp::ISC_TICKS_PER_DAY;
|
||||
|
||||
atDisplacement = getDisplacement(tempTimeStampUtc, atTimeZone);
|
||||
}
|
||||
|
||||
ticks -= -atDisplacement * 60 * ISC_TIME_SECONDS_PRECISION;
|
||||
|
||||
ISC_TIMESTAMP ts;
|
||||
ts.timestamp_date = ticks / TimeStamp::ISC_TICKS_PER_DAY;
|
||||
@ -160,12 +228,110 @@ ISC_TIMESTAMP TimeZoneUtil::timeStampTzAtZone(const ISC_TIMESTAMP_TZ& timeStampT
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
static void skipSpaces(const char*& p, const char* end)
|
||||
// Gets the displacement that a timestamp (with time zone) is from UTC.
|
||||
static SSHORT getDisplacement(const ISC_TIMESTAMP_TZ& timeStampTz)
|
||||
{
|
||||
while (p < end && (*p == ' ' || *p == '\t'))
|
||||
++p;
|
||||
const USHORT timeZone = timeStampTz.timestamp_zone;
|
||||
|
||||
if (isOffset(timeZone))
|
||||
return offsetZoneToDisplacement(timeZone);
|
||||
else
|
||||
{
|
||||
//// FIXME:
|
||||
switch (MAX_USHORT - timeZone)
|
||||
{
|
||||
case 0: // BRT
|
||||
return -(3 * 60);
|
||||
|
||||
case 1: // BRST
|
||||
return -(2 * 60);
|
||||
|
||||
default:
|
||||
fb_assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the displacement necessary to convert a UTC timestamp to another time zone.
|
||||
static SSHORT getDisplacement(const ISC_TIMESTAMP& timeStampUtc, USHORT timeZone)
|
||||
{
|
||||
if (isOffset(timeZone))
|
||||
return offsetZoneToDisplacement(timeZone);
|
||||
else
|
||||
{
|
||||
//// FIXME:
|
||||
switch (MAX_USHORT - timeZone)
|
||||
{
|
||||
case 0: // BRT
|
||||
return -(3 * 60);
|
||||
|
||||
case 1: // BRST
|
||||
return -(2 * 60);
|
||||
|
||||
default:
|
||||
fb_assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the time zone is offset-based or false if region-based.
|
||||
static inline bool isOffset(USHORT timeZone)
|
||||
{
|
||||
return timeZone <= TimeZoneUtil::ONE_DAY * 2;
|
||||
}
|
||||
|
||||
// Makes a time zone id from offsets.
|
||||
static USHORT makeFromOffset(int sign, unsigned tzh, unsigned tzm)
|
||||
{
|
||||
if (!TimeZoneUtil::isValidOffset(sign, tzh, tzm))
|
||||
status_exception::raise(Arg::Gds(isc_random) << "Invalid time zone offset"); //// TODO:
|
||||
|
||||
return (USHORT)((tzh * 60 + tzm) * sign + TimeZoneUtil::ONE_DAY);
|
||||
}
|
||||
|
||||
// Makes a time zone id from a region.
|
||||
static USHORT makeFromRegion(const char* str, unsigned strLen)
|
||||
{
|
||||
const char* end = str + strLen;
|
||||
|
||||
skipSpaces(str, end);
|
||||
|
||||
const char* start = str;
|
||||
|
||||
while (str < end && ((*str >= 'a' && *str <= 'z') || (*str >= 'A' && *str <= 'Z')))
|
||||
++str;
|
||||
|
||||
unsigned len = str - start;
|
||||
|
||||
skipSpaces(str, end);
|
||||
|
||||
if (str == end)
|
||||
{
|
||||
//// FIXME:
|
||||
for (unsigned i = 0; i < FB_NELEM(TIME_ZONE_LIST); ++i)
|
||||
{
|
||||
const char* abbr = TIME_ZONE_LIST[i].abbr;
|
||||
|
||||
if (len == strlen(abbr) && fb_utils::strnicmp(start, abbr, len) == 0)
|
||||
return MAX_USHORT - i;
|
||||
}
|
||||
}
|
||||
|
||||
status_exception::raise(Arg::Gds(isc_random) << "Invalid time zone region"); //// TODO:
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Gets the displacement from a offset-based time zone id.
|
||||
static inline SSHORT offsetZoneToDisplacement(USHORT timeZone)
|
||||
{
|
||||
fb_assert(isOffset(timeZone));
|
||||
|
||||
return (SSHORT) (int(timeZone) - TimeZoneUtil::ONE_DAY);
|
||||
}
|
||||
|
||||
// Parses a integer number.
|
||||
static int parseNumber(const char*& p, const char* end)
|
||||
{
|
||||
const char* start = p;
|
||||
@ -179,3 +345,10 @@ static int parseNumber(const char*& p, const char* end)
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// Skip spaces and tabs.
|
||||
static void skipSpaces(const char*& p, const char* end)
|
||||
{
|
||||
while (p < end && (*p == ' ' || *p == '\t'))
|
||||
++p;
|
||||
}
|
||||
|
@ -34,21 +34,22 @@ namespace Firebird {
|
||||
class TimeZoneUtil
|
||||
{
|
||||
public:
|
||||
static const USHORT UTC_ZONE = 0;
|
||||
static const unsigned ONE_DAY = 24 * 60; // used for offset encoding
|
||||
static const USHORT UTC_ZONE = ONE_DAY + 0;
|
||||
static const unsigned MAX_LEN = 6;
|
||||
|
||||
public:
|
||||
static USHORT getCurrent();
|
||||
|
||||
static USHORT parse(const char* str, unsigned strLen);
|
||||
static unsigned format(char* buffer, USHORT zone);
|
||||
static unsigned format(char* buffer, size_t bufferSize, USHORT timeZone);
|
||||
|
||||
static bool isValidOffset(int sign, int tzh, unsigned tzm);
|
||||
static bool isValidOffset(int sign, unsigned tzh, unsigned tzm);
|
||||
|
||||
static void extractOffset(const ISC_TIMESTAMP_TZ& timeStampTz, int* sign, unsigned* tzh, unsigned* tzm);
|
||||
|
||||
static ISC_TIME timeTzAtZone(const ISC_TIME_TZ& timeTz, USHORT zone);
|
||||
static ISC_TIMESTAMP timeStampTzAtZone(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT zone);
|
||||
static ISC_TIME timeTzAtZone(const ISC_TIME_TZ& timeTz, USHORT atTimeZone);
|
||||
static ISC_TIMESTAMP timeStampTzAtZone(const ISC_TIMESTAMP_TZ& timeStampTz, USHORT atTimeZone);
|
||||
};
|
||||
|
||||
} // namespace Firebird
|
||||
|
@ -2298,7 +2298,7 @@ static void datetime_to_text(const dsc* from, dsc* to, Callbacks* cb)
|
||||
if (from->dsc_dtype == dtype_sql_time_tz || from->dsc_dtype == dtype_timestamp_tz)
|
||||
{
|
||||
*p++ = ' ';
|
||||
p += TimeZoneUtil::format(p, timezone);
|
||||
p += TimeZoneUtil::format(p, sizeof(temp) - (p - temp), timezone);
|
||||
}
|
||||
|
||||
// Move the text version of the date/time value into the destination
|
||||
|
@ -7428,8 +7428,9 @@ static unsigned print_item(TEXT** s, const IsqlVar* var, const unsigned length)
|
||||
|
||||
if (dtype == SQL_TIMESTAMP_TZ)
|
||||
{
|
||||
strcat(d + strlen(d), " ");
|
||||
TimeZoneUtil::format(d + strlen(d), var->value.asDateTimeTz->timestamp_zone);
|
||||
size_t len = strlen(d);
|
||||
strcat(d + len, " ");
|
||||
TimeZoneUtil::format(d + len + 1, sizeof(d) - (len + 1), var->value.asDateTimeTz->timestamp_zone);
|
||||
}
|
||||
|
||||
sprintf(p, "%-*.*s ", length, length, d);
|
||||
@ -7448,8 +7449,9 @@ static unsigned print_item(TEXT** s, const IsqlVar* var, const unsigned length)
|
||||
|
||||
if (dtype == SQL_TIME_TZ)
|
||||
{
|
||||
strcat(d + strlen(d), " ");
|
||||
TimeZoneUtil::format(d + strlen(d), var->value.asTimeTz->time_zone);
|
||||
size_t len = strlen(d);
|
||||
strcat(d + len, " ");
|
||||
TimeZoneUtil::format(d + len + 1, sizeof(d) - (len + 1), var->value.asTimeTz->time_zone);
|
||||
}
|
||||
|
||||
sprintf(p, "%-*.*s ", length, length, d);
|
||||
|
Loading…
Reference in New Issue
Block a user