⛏️ index : haiku.git

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


#include "ICUTimeData.h"

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

#include <unicode/dtfmtsym.h>
#include <unicode/gregocal.h>
#include <unicode/smpdtfmt.h>

#include <AutoDeleter.h>

#include "ICUMessagesData.h"


U_NAMESPACE_USE


namespace BPrivate {
namespace Libroot {


ICUTimeData::ICUTimeData(pthread_key_t tlsKey, struct lc_time_t& lcTimeInfo,
	const ICUMessagesData& messagesData)
	:
	inherited(tlsKey),
	fLCTimeInfo(lcTimeInfo),
	fDataBridge(NULL),
	fMessagesData(messagesData)
{
	for (int i = 0; i < 12; ++i) {
		fLCTimeInfo.mon[i] = fMon[i];
		fLCTimeInfo.month[i] = fMonth[i];
		fLCTimeInfo.alt_month[i] = fAltMonth[i];
	}
	for (int i = 0; i < 7; ++i) {
		fLCTimeInfo.wday[i] = fWday[i];
		fLCTimeInfo.weekday[i] = fWeekday[i];
	}
	fLCTimeInfo.X_fmt = fTimeFormat;
	fLCTimeInfo.x_fmt = fDateFormat;
	fLCTimeInfo.c_fmt = fDateTimeFormat;
	fLCTimeInfo.am = fAm;
	fLCTimeInfo.pm = fPm;
	fLCTimeInfo.date_fmt = fDateTimeZoneFormat;
	fLCTimeInfo.md_order = fMonthDayOrder;
	fLCTimeInfo.ampm_fmt = fAmPmFormat;
}


ICUTimeData::~ICUTimeData()
{
}


void
ICUTimeData::Initialize(LocaleTimeDataBridge* dataBridge)
{
	fDataBridge = dataBridge;
}


status_t
ICUTimeData::SetTo(const Locale& locale, const char* posixLocaleName)
{
	status_t result = inherited::SetTo(locale, posixLocaleName);
	if (result != B_OK)
		return result;

	UErrorCode icuStatus = U_ZERO_ERROR;
	DateFormatSymbols formatSymbols(ICULocaleForStrings(), icuStatus);
	if (!U_SUCCESS(icuStatus))
		return B_UNSUPPORTED;

	int count = 0;
	const UnicodeString* strings = formatSymbols.getShortMonths(count);
	result = _SetLCTimeEntries(strings, fMon[0], sizeof(fMon[0]), count, 12);

	if (result == B_OK) {
		strings = formatSymbols.getMonths(count);
		result = _SetLCTimeEntries(strings, fMonth[0], sizeof(fMonth[0]), count,
			12);
	}

	if (result == B_OK) {
		strings = formatSymbols.getShortWeekdays(count);
		if (count == 8 && strings[0].length() == 0) {
			// ICUs weekday arrays are 1-based
			strings++;
			count = 7;
		}
		result
			= _SetLCTimeEntries(strings, fWday[0], sizeof(fWday[0]), count, 7);
	}

	if (result == B_OK) {
		strings = formatSymbols.getWeekdays(count);
		if (count == 8 && strings[0].length() == 0) {
			// ICUs weekday arrays are 1-based
			strings++;
			count = 7;
		}
		result = _SetLCTimeEntries(strings, fWeekday[0], sizeof(fWeekday[0]),
			count, 7);
	}

	if (result == B_OK) {
		try {
			DateFormat* format = DateFormat::createTimeInstance(
				DateFormat::kDefault, fLocale);
			result = _SetLCTimePattern(format, fTimeFormat, sizeof(fTimeFormat));
			delete format;
		} catch(...) {
			result = B_NO_MEMORY;
		}
	}

	if (result == B_OK) {
		try {
			DateFormat* format = DateFormat::createDateInstance(
				DateFormat::kDefault, fLocale);
			result = _SetLCTimePattern(format, fDateFormat, sizeof(fDateFormat));
			delete format;
		} catch(...) {
			result = B_NO_MEMORY;
		}
	}

	if (result == B_OK) {
		try {
			DateFormat* format = DateFormat::createDateTimeInstance(
				DateFormat::kDefault, DateFormat::kDefault, fLocale);
			result = _SetLCTimePattern(format, fDateTimeFormat,
				sizeof(fDateTimeFormat));
			delete format;
		} catch(...) {
			result = B_NO_MEMORY;
		}
	}

	if (result == B_OK) {
		strings = formatSymbols.getAmPmStrings(count);
		result = _SetLCTimeEntries(strings, fAm, sizeof(fAm), 1, 1);
		if (result == B_OK)
			result = _SetLCTimeEntries(&strings[1], fPm, sizeof(fPm), 1, 1);
	}

	if (result == B_OK) {
		strings = formatSymbols.getMonths(count, DateFormatSymbols::STANDALONE,
			DateFormatSymbols::WIDE);
		result = _SetLCTimeEntries(strings, fAltMonth[0], sizeof(fAltMonth[0]),
			count, 12);
	}

	strcpy(fAmPmFormat, fDataBridge->posixLCTimeInfo->ampm_fmt);
		// ICU does not provide anything for this (and that makes sense, too)

	return result;
}


status_t
ICUTimeData::SetToPosix()
{
	status_t result = inherited::SetToPosix();

	if (result == B_OK) {
		for (int i = 0; i < 12; ++i) {
			strcpy(fMon[i], fDataBridge->posixLCTimeInfo->mon[i]);
			strcpy(fMonth[i], fDataBridge->posixLCTimeInfo->month[i]);
			strcpy(fAltMonth[i], fDataBridge->posixLCTimeInfo->alt_month[i]);
		}
		for (int i = 0; i < 7; ++i) {
			strcpy(fWday[i], fDataBridge->posixLCTimeInfo->wday[i]);
			strcpy(fWeekday[i], fDataBridge->posixLCTimeInfo->weekday[i]);
		}
		strcpy(fTimeFormat, fDataBridge->posixLCTimeInfo->X_fmt);
		strcpy(fDateFormat, fDataBridge->posixLCTimeInfo->x_fmt);
		strcpy(fDateTimeFormat, fDataBridge->posixLCTimeInfo->c_fmt);
		strcpy(fAm, fDataBridge->posixLCTimeInfo->am);
		strcpy(fPm, fDataBridge->posixLCTimeInfo->pm);
		strcpy(fDateTimeZoneFormat, fDataBridge->posixLCTimeInfo->date_fmt);
		strcpy(fMonthDayOrder, fDataBridge->posixLCTimeInfo->md_order);
		strcpy(fAmPmFormat, fDataBridge->posixLCTimeInfo->ampm_fmt);
	}

	return result;
}


const char*
ICUTimeData::GetLanginfo(int index)
{
	switch(index) {
		case D_T_FMT:
			return fDateTimeFormat;
		case D_FMT:
			return fDateFormat;
		case T_FMT:
			return fTimeFormat;
		case T_FMT_AMPM:
			return fAmPmFormat;
		case AM_STR:
			return fAm;
		case PM_STR:
			return fPm;

		case DAY_1:
		case DAY_2:
		case DAY_3:
		case DAY_4:
		case DAY_5:
		case DAY_6:
		case DAY_7:
			return fWeekday[index - DAY_1];

		case ABDAY_1:
		case ABDAY_2:
		case ABDAY_3:
		case ABDAY_4:
		case ABDAY_5:
		case ABDAY_6:
		case ABDAY_7:
			return fWday[index - ABDAY_1];

		case MON_1:
		case MON_2:
		case MON_3:
		case MON_4:
		case MON_5:
		case MON_6:
		case MON_7:
		case MON_8:
		case MON_9:
		case MON_10:
		case MON_11:
		case MON_12:
			return fMonth[index - MON_1];

		case ABMON_1:
		case ABMON_2:
		case ABMON_3:
		case ABMON_4:
		case ABMON_5:
		case ABMON_6:
		case ABMON_7:
		case ABMON_8:
		case ABMON_9:
		case ABMON_10:
		case ABMON_11:
		case ABMON_12:
			return fMon[index - ABMON_1];

		default:
			return "";
	}
}


const Locale&
ICUTimeData::ICULocaleForStrings() const
{
	// check if the date strings should be taken from the messages-locale
	// or from the time-locale (default)
	UErrorCode icuStatus = U_ZERO_ERROR;
	char stringsValue[16];
	fLocale.getKeywordValue("strings", stringsValue, sizeof(stringsValue),
		icuStatus);
	if (U_SUCCESS(icuStatus) && strcasecmp(stringsValue, "messages") == 0)
		return fMessagesData.ICULocale();
	else
		return fLocale;
}


status_t
ICUTimeData::_SetLCTimeEntries(const UnicodeString* strings, char* destination,
	int entrySize, int count, int maxCount)
{
	if (strings == NULL)
		return B_ERROR;

	status_t result = B_OK;
	if (count > maxCount)
		count = maxCount;
	for (int32 i = 0; result == B_OK && i < count; ++i) {
		result = _ConvertUnicodeStringToLocaleconvEntry(strings[i], destination,
			entrySize);
		destination += entrySize;
	}

	return result;
}


status_t
ICUTimeData::_SetLCTimePattern(DateFormat* format, char* destination,
	int destinationSize)
{
	SimpleDateFormat* simpleFormat = dynamic_cast<SimpleDateFormat*>(format);
	if (!simpleFormat)
		return B_BAD_TYPE;

	// convert ICU-type pattern to posix (i.e. strftime()) format string
	UnicodeString icuPattern;
	simpleFormat->toPattern(icuPattern);
	UnicodeString posixPattern;
	if (icuPattern.length() > 0) {
		UChar lastCharSeen = 0;
		int lastCharCount = 1;
		bool inSingleQuotes = false;
		bool inDoubleQuotes = false;
		// we loop one character past the end on purpose, which will result in a
		// final -1 char to be processed, which in turn will let us handle the
		// last character (via lastCharSeen)
		for (int i = 0; i <= icuPattern.length(); ++i) {
			UChar currChar = icuPattern.charAt(i);
			if (u_isspace(currChar))
				currChar = ' ';

			if (lastCharSeen != 0 && currChar == lastCharSeen) {
				lastCharCount++;
				continue;
			}

			if (!inSingleQuotes && !inDoubleQuotes) {
				switch (lastCharSeen) {
					case L'a':
						posixPattern.append(UnicodeString("%p", ""));
						break;
					case L'd':
						if (lastCharCount == 2)
							posixPattern.append(UnicodeString("%d", ""));
						else
							posixPattern.append(UnicodeString("%e", ""));
						break;
					case L'D':
						posixPattern.append(UnicodeString("%j", ""));
						break;
					case L'c':
						// fall through, to handle 'c' the same as 'e'
					case L'e':
						if (lastCharCount == 4)
							posixPattern.append(UnicodeString("%A", ""));
						else if (lastCharCount <= 2)
							posixPattern.append(UnicodeString("%u", ""));
						else
							posixPattern.append(UnicodeString("%a", ""));
						break;
					case L'E':
						if (lastCharCount == 4)
							posixPattern.append(UnicodeString("%A", ""));
						else
							posixPattern.append(UnicodeString("%a", ""));
						break;
					case L'k':
						// fall through, to handle 'k' the same as 'h'
					case L'h':
						if (lastCharCount == 2)
							posixPattern.append(UnicodeString("%I", ""));
						else
							posixPattern.append(UnicodeString("%l", ""));
						break;
					case L'H':
						if (lastCharCount == 2)
							posixPattern.append(UnicodeString("%H", ""));
						else
							posixPattern.append(UnicodeString("%k", ""));
						break;
					case L'm':
						posixPattern.append(UnicodeString("%M", ""));
						break;
					case L'L':
						// fall through, to handle 'L' the same as 'M'
					case L'M':
						if (lastCharCount == 4)
							posixPattern.append(UnicodeString("%B", ""));
						else if (lastCharCount == 3)
							posixPattern.append(UnicodeString("%b", ""));
						else
							posixPattern.append(UnicodeString("%m", ""));
						break;
					case L's':
						posixPattern.append(UnicodeString("%S", ""));
						break;
					case L'w':
						posixPattern.append(UnicodeString("%V", ""));
						break;
					case L'y':
						if (lastCharCount == 2)
							posixPattern.append(UnicodeString("%y", ""));
						else
							posixPattern.append(UnicodeString("%Y", ""));
						break;
					case L'Y':
						posixPattern.append(UnicodeString("%G", ""));
						break;
					case L'z':
						posixPattern.append(UnicodeString("%Z", ""));
						break;
					case L'Z':
						posixPattern.append(UnicodeString("%z", ""));
						break;
					default:
						if (lastCharSeen != 0)
							posixPattern.append(lastCharSeen);
				}
			} else {
				if (lastCharSeen != 0)
					posixPattern.append(lastCharSeen);
			}

			if (currChar == L'"') {
				inDoubleQuotes = !inDoubleQuotes;
				lastCharSeen = 0;
			} else if (currChar == L'\'') {
				inSingleQuotes = !inSingleQuotes;
				lastCharSeen = 0;
			} else
				lastCharSeen = currChar;

			lastCharCount = 1;
		}
	}

	return _ConvertUnicodeStringToLocaleconvEntry(posixPattern, destination,
		destinationSize);
}


}	// namespace Libroot
}	// namespace BPrivate