⛏️ index : haiku.git

/*
 * Copyright 2010-2011, Oliver Tappe, zooey@hirschkaefer.de.
 * Distributed under the terms of the MIT License.
 */


#include "ICUTimeConversion.h"

#include <math.h>
#include <string.h>
#include <strings.h>

#include <unicode/gregocal.h>


U_NAMESPACE_USE


namespace BPrivate {
namespace Libroot {


ICUTimeConversion::ICUTimeConversion(const ICUTimeData& timeData)
	:
	fTimeData(timeData),
	fDataBridge(NULL),
	fTimeZone(NULL)
{
	fTimeZoneID[0] = '\0';
}


ICUTimeConversion::~ICUTimeConversion()
{
	delete fTimeZone;
}


void
ICUTimeConversion::Initialize(TimeConversionDataBridge* dataBridge)
{
	fDataBridge = dataBridge;
}


status_t
ICUTimeConversion::TZSet(const char* timeZoneID, const char* tz)
{
	bool offsetHasBeenSet = false;
	bool timeZoneIdMatches = false;

	// The given TZ environment variable's content overrides the default
	// system timezone.
	if (tz != NULL) {
		// If the value given in the TZ env-var starts with a colon, that
		// value is implementation specific, we expect a full timezone ID.
		if (*tz == ':') {
			// nothing to do if the given name matches the current timezone
			if (strcasecmp(fTimeZoneID, tz + 1) == 0) {
				timeZoneIdMatches = true;
				goto done;
			}

			strlcpy(fTimeZoneID, tz + 1, sizeof(fTimeZoneID));
		} else {
			// note timezone name
			strlcpy(fTimeZoneID, tz, sizeof(fTimeZoneID));

			// nothing to do if the given name matches the current timezone
			if (strcasecmp(fTimeZoneID, fDataBridge->addrOfTZName[0]) == 0) {
				timeZoneIdMatches = true;
				goto done;
			}

			// parse TZ variable (only <std> and <offset> supported)
			const char* tzNameEnd = tz;
			while(isalpha(*tzNameEnd))
				++tzNameEnd;
			if (*tzNameEnd == '-' || *tzNameEnd == '+') {
				int hours = 0;
				int minutes = 0;
				int seconds = 0;
				sscanf(tzNameEnd + 1, "%2d:%2d:%2d", &hours, &minutes,
					&seconds);
				hours = min_c(24, max_c(0, hours));
				minutes = min_c(59, max_c(0, minutes));
				seconds = min_c(59, max_c(0, seconds));

				*fDataBridge->addrOfTimezone = (*tzNameEnd == '-' ? -1 : 1)
					* (hours * 3600 + minutes * 60 + seconds);
				offsetHasBeenSet = true;
			}
		}
	} else {
		// nothing to do if the given name matches the current timezone
		if (strcasecmp(fTimeZoneID, timeZoneID) == 0) {
			timeZoneIdMatches = true;
			goto done;
		}

		strlcpy(fTimeZoneID, timeZoneID, sizeof(fTimeZoneID));
	}

done:
	// fTimeZone can still be NULL if we don't initialize it
	// in the first TZSet, causing problems for future
	// Localtime invocations.
	if (fTimeZone != NULL && timeZoneIdMatches)
		return B_OK;

	delete fTimeZone;
	fTimeZone = TimeZone::createTimeZone(fTimeZoneID);
	if (fTimeZone == NULL)
		return B_NO_MEMORY;

	UnicodeString temp;
	if (fTimeZone->getID(temp) == UCAL_UNKNOWN_ZONE_ID)
		goto error;

	if (offsetHasBeenSet) {
		fTimeZone->setRawOffset(*fDataBridge->addrOfTimezone * -1 * 1000);
	} else {
		int32_t rawOffset;
		int32_t dstOffset;
		UDate nowMillis = 1000 * (UDate)time(NULL);
		UErrorCode icuStatus = U_ZERO_ERROR;
		fTimeZone->getOffset(nowMillis, FALSE, rawOffset, dstOffset, icuStatus);
		if (!U_SUCCESS(icuStatus))
			goto error;
		*fDataBridge->addrOfTimezone = -1 * (rawOffset + dstOffset) / 1000;
			// we want seconds, not the ms that ICU gives us
	}

	*fDataBridge->addrOfDaylight = fTimeZone->useDaylightTime();

	for (int i = 0; i < 2; ++i) {
		if (tz != NULL && *tz != ':' && i == 0) {
			strlcpy(fDataBridge->addrOfTZName[0], fTimeZoneID,
				fDataBridge->kTZNameLength);
		} else {
			UnicodeString icuString;
			fTimeZone->getDisplayName(i == 1, TimeZone::SHORT,
				fTimeData.ICULocaleForStrings(), icuString);
			CheckedArrayByteSink byteSink(fDataBridge->addrOfTZName[i],
				fDataBridge->kTZNameLength);
			icuString.toUTF8(byteSink);

			if (byteSink.Overflowed())
				goto error;

			// make sure to canonicalize "GMT+00:00" to just "GMT"
			if (strcmp(fDataBridge->addrOfTZName[i], "GMT+00:00") == 0)
				fDataBridge->addrOfTZName[i][3] = '\0';
		}
	}

	return B_OK;

error:
	*fDataBridge->addrOfTimezone = 0;
	*fDataBridge->addrOfDaylight = false;
	strcpy(fDataBridge->addrOfTZName[0], "GMT");
	strcpy(fDataBridge->addrOfTZName[1], "GMT");

	return B_ERROR;
}


status_t
ICUTimeConversion::Localtime(const time_t* inTime, struct tm* tmOut)
{
	if (fTimeZone == NULL)
		return B_NO_INIT;

	tmOut->tm_zone = fTimeZoneID;
	return _FillTmValues(fTimeZone, inTime, tmOut);
}


status_t
ICUTimeConversion::Gmtime(const time_t* inTime, struct tm* tmOut)
{
	const TimeZone* icuTimeZone = TimeZone::getGMT();
		// no delete - doesn't belong to us

	status_t status = _FillTmValues(icuTimeZone, inTime, tmOut);
	if (status == B_OK) {
		// tm_zone must be "GMT" for gmtime, not the current timezone
		// (even if that happens to be equivalent to GMT).
		tmOut->tm_zone = (char*)"GMT";
	}
	return status;
}


status_t
ICUTimeConversion::Mktime(struct tm* inOutTm, time_t& timeOut)
{
	return _Mktime(fTimeZone, inOutTm, timeOut);
}


status_t
ICUTimeConversion::Timegm(struct tm* inOutTm, time_t& timeOut)
{
	const TimeZone* icuTimeZone = TimeZone::getGMT();
	return _Mktime(icuTimeZone, inOutTm, timeOut);
}


status_t
ICUTimeConversion::_FillTmValues(const TimeZone* icuTimeZone,
	const time_t* inTime, struct tm* tmOut)
{
	UErrorCode icuStatus = U_ZERO_ERROR;
	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(), icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;

	calendar.setTime(1000 * (UDate)*inTime, icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;

	tmOut->tm_sec = calendar.get(UCAL_SECOND, icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_min = calendar.get(UCAL_MINUTE, icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_hour = calendar.get(UCAL_HOUR_OF_DAY, icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_mday = calendar.get(UCAL_DAY_OF_MONTH, icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_mon = calendar.get(UCAL_MONTH, icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_year = calendar.get(UCAL_YEAR, icuStatus) - 1900;
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_wday = calendar.get(UCAL_DAY_OF_WEEK, icuStatus) - 1;
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_yday = calendar.get(UCAL_DAY_OF_YEAR, icuStatus) - 1;
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_isdst = calendar.inDaylightTime(icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	tmOut->tm_gmtoff = (calendar.get(UCAL_ZONE_OFFSET, icuStatus)
		+ calendar.get(UCAL_DST_OFFSET, icuStatus)) / 1000;
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;

	tmOut->tm_zone = fDataBridge->addrOfTZName[tmOut->tm_isdst ? 1 : 0];
	return B_OK;
}


status_t
ICUTimeConversion::_Mktime(const TimeZone* icuTimeZone,
	struct tm* inOutTm, time_t& timeOut)
{
	if (icuTimeZone == NULL)
		return B_NO_INIT;

	UErrorCode icuStatus = U_ZERO_ERROR;
	GregorianCalendar calendar(*icuTimeZone, fTimeData.ICULocale(),
		icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;

	calendar.setLenient(TRUE);
	calendar.set(inOutTm->tm_year + 1900, inOutTm->tm_mon, inOutTm->tm_mday,
		inOutTm->tm_hour, inOutTm->tm_min, inOutTm->tm_sec);

	UDate timeInMillis = calendar.getTime(icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_ERROR;
	timeOut = (time_t)((int64_t)timeInMillis / 1000);

	return _FillTmValues(icuTimeZone, &timeOut, inOutTm);
}


}	// namespace Libroot
}	// namespace BPrivate