8
0
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:
Adriano dos Santos Fernandes 2018-02-23 16:14:54 -03:00
parent 4812ce8ee8
commit 8792dff315
4 changed files with 239 additions and 63 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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);