3531c28ea0
X-SVN-Rev: 26116
469 lines
15 KiB
C++
469 lines
15 KiB
C++
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 2007-2009, International Business Machines Corporation and *
|
|
* others. All Rights Reserved. *
|
|
*******************************************************************************
|
|
*/
|
|
|
|
#include "unicode/utypes.h"
|
|
|
|
#if !UCONFIG_NO_FORMATTING
|
|
|
|
//#define DEBUG_RELDTFMT
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "reldtfmt.h"
|
|
#include "unicode/msgfmt.h"
|
|
#include "unicode/smpdtfmt.h"
|
|
|
|
#include "gregoimp.h" // for CalendarData
|
|
#include "cmemory.h"
|
|
|
|
U_NAMESPACE_BEGIN
|
|
|
|
|
|
/**
|
|
* An array of URelativeString structs is used to store the resource data loaded out of the bundle.
|
|
*/
|
|
struct URelativeString {
|
|
int32_t offset; /** offset of this item, such as, the relative date **/
|
|
int32_t len; /** length of the string **/
|
|
const UChar* string; /** string, or NULL if not set **/
|
|
};
|
|
|
|
static const char DT_DateTimePatternsTag[]="DateTimePatterns";
|
|
|
|
|
|
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)
|
|
|
|
RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) :
|
|
DateFormat(other), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL),
|
|
fDateStyle(other.fDateStyle), fTimeStyle(other.fTimeStyle), fLocale(other.fLocale),
|
|
fDayMin(other.fDayMin), fDayMax(other.fDayMax),
|
|
fDatesLen(other.fDatesLen), fDates(NULL)
|
|
{
|
|
if(other.fDateFormat != NULL) {
|
|
fDateFormat = (DateFormat*)other.fDateFormat->clone();
|
|
} else {
|
|
fDateFormat = NULL;
|
|
}
|
|
if (fDatesLen > 0) {
|
|
fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen);
|
|
uprv_memcpy(fDates, other.fDates, sizeof(fDates[0])*fDatesLen);
|
|
}
|
|
//fCalendar = other.fCalendar->clone();
|
|
/*
|
|
if(other.fTimeFormat != NULL) {
|
|
fTimeFormat = (DateFormat*)other.fTimeFormat->clone();
|
|
} else {
|
|
fTimeFormat = NULL;
|
|
}
|
|
*/
|
|
}
|
|
|
|
RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const Locale& locale, UErrorCode& status)
|
|
: DateFormat(), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL),
|
|
fDateStyle(dateStyle), fTimeStyle(timeStyle), fLocale(locale), fDatesLen(0), fDates(NULL)
|
|
{
|
|
if(U_FAILURE(status) ) {
|
|
return;
|
|
}
|
|
|
|
if(fDateStyle != UDAT_NONE) {
|
|
EStyle newStyle = (EStyle)(fDateStyle & ~UDAT_RELATIVE);
|
|
// Create a DateFormat in the non-relative style requested.
|
|
fDateFormat = createDateInstance(newStyle, locale);
|
|
}
|
|
if(fTimeStyle >= UDAT_FULL && fTimeStyle <= UDAT_SHORT) {
|
|
fTimeFormat = createTimeInstance((EStyle)fTimeStyle, locale);
|
|
} else if(fTimeStyle != UDAT_NONE) {
|
|
// don't support other time styles (e.g. relative styles), for now
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
|
|
// Initialize the parent fCalendar, so that parse() works correctly.
|
|
initializeCalendar(NULL, locale, status);
|
|
loadDates(status);
|
|
}
|
|
|
|
RelativeDateFormat::~RelativeDateFormat() {
|
|
delete fDateFormat;
|
|
delete fTimeFormat;
|
|
delete fCombinedFormat;
|
|
uprv_free(fDates);
|
|
}
|
|
|
|
|
|
Format* RelativeDateFormat::clone(void) const {
|
|
return new RelativeDateFormat(*this);
|
|
}
|
|
|
|
UBool RelativeDateFormat::operator==(const Format& other) const {
|
|
if(DateFormat::operator==(other)) {
|
|
// DateFormat::operator== guarantees following cast is safe
|
|
RelativeDateFormat* that = (RelativeDateFormat*)&other;
|
|
return (fDateStyle==that->fDateStyle &&
|
|
fTimeStyle==that->fTimeStyle &&
|
|
fLocale==that->fLocale);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
UnicodeString& RelativeDateFormat::format( Calendar& cal,
|
|
UnicodeString& appendTo,
|
|
FieldPosition& pos) const {
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
UChar emptyStr = 0;
|
|
UnicodeString dateString(&emptyStr);
|
|
|
|
// calculate the difference, in days, between 'cal' and now.
|
|
int dayDiff = dayDifference(cal, status);
|
|
|
|
// look up string
|
|
int32_t len = 0;
|
|
const UChar *theString = getStringForDay(dayDiff, len, status);
|
|
if(U_SUCCESS(status) && (theString!=NULL)) {
|
|
// found a relative string
|
|
dateString.setTo(theString, len);
|
|
}
|
|
|
|
if(fTimeFormat == NULL || fCombinedFormat == 0) {
|
|
if (dateString.length() > 0) {
|
|
appendTo.append(dateString);
|
|
} else if(fDateFormat != NULL) {
|
|
fDateFormat->format(cal,appendTo,pos);
|
|
}
|
|
} else {
|
|
if (dateString.length() == 0 && fDateFormat != NULL) {
|
|
fDateFormat->format(cal,dateString,pos);
|
|
}
|
|
UnicodeString timeString(&emptyStr);
|
|
FieldPosition timepos = pos;
|
|
fTimeFormat->format(cal,timeString,timepos);
|
|
Formattable timeDateStrings[] = { timeString, dateString };
|
|
fCombinedFormat->format(timeDateStrings, 2, appendTo, pos, status); // pos is ignored by this
|
|
int32_t offset;
|
|
if (pos.getEndIndex() > 0 && (offset = appendTo.indexOf(dateString)) >= 0) {
|
|
// pos.field was found in dateString, offset start & end based on final position of dateString
|
|
pos.setBeginIndex( pos.getBeginIndex() + offset );
|
|
pos.setEndIndex( pos.getEndIndex() + offset );
|
|
} else if (timepos.getEndIndex() > 0 && (offset = appendTo.indexOf(timeString)) >= 0) {
|
|
// pos.field was found in timeString, offset start & end based on final position of timeString
|
|
pos.setBeginIndex( timepos.getBeginIndex() + offset );
|
|
pos.setEndIndex( timepos.getEndIndex() + offset );
|
|
}
|
|
}
|
|
|
|
return appendTo;
|
|
}
|
|
|
|
|
|
|
|
UnicodeString&
|
|
RelativeDateFormat::format(const Formattable& obj,
|
|
UnicodeString& appendTo,
|
|
FieldPosition& pos,
|
|
UErrorCode& status) const
|
|
{
|
|
// this is just here to get around the hiding problem
|
|
// (the previous format() override would hide the version of
|
|
// format() on DateFormat that this function correspond to, so we
|
|
// have to redefine it here)
|
|
return DateFormat::format(obj, appendTo, pos, status);
|
|
}
|
|
|
|
|
|
void RelativeDateFormat::parse( const UnicodeString& text,
|
|
Calendar& cal,
|
|
ParsePosition& pos) const {
|
|
|
|
// Can the fDateFormat parse it?
|
|
if(fDateFormat != NULL) {
|
|
ParsePosition aPos(pos);
|
|
fDateFormat->parse(text,cal,aPos);
|
|
if((aPos.getIndex() != pos.getIndex()) &&
|
|
(aPos.getErrorIndex()==-1)) {
|
|
pos=aPos; // copy the sub parse
|
|
return; // parsed subfmt OK
|
|
}
|
|
}
|
|
|
|
// Linear search the relative strings
|
|
for(int n=0;n<fDatesLen;n++) {
|
|
if(fDates[n].string != NULL &&
|
|
(0==text.compare(pos.getIndex(),
|
|
fDates[n].len,
|
|
fDates[n].string))) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
|
|
// Set the calendar to now+offset
|
|
cal.setTime(Calendar::getNow(),status);
|
|
cal.add(UCAL_DATE,fDates[n].offset, status);
|
|
|
|
if(U_FAILURE(status)) {
|
|
// failure in setting calendar fields
|
|
pos.setErrorIndex(pos.getIndex()+fDates[n].len);
|
|
} else {
|
|
pos.setIndex(pos.getIndex()+fDates[n].len);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// parse failed
|
|
}
|
|
|
|
UDate
|
|
RelativeDateFormat::parse( const UnicodeString& text,
|
|
ParsePosition& pos) const {
|
|
// redefined here because the other parse() function hides this function's
|
|
// cunterpart on DateFormat
|
|
return DateFormat::parse(text, pos);
|
|
}
|
|
|
|
UDate
|
|
RelativeDateFormat::parse(const UnicodeString& text, UErrorCode& status) const
|
|
{
|
|
// redefined here because the other parse() function hides this function's
|
|
// counterpart on DateFormat
|
|
return DateFormat::parse(text, status);
|
|
}
|
|
|
|
|
|
const UChar *RelativeDateFormat::getStringForDay(int32_t day, int32_t &len, UErrorCode &status) const {
|
|
if(U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Is it outside the resource bundle's range?
|
|
if(day < fDayMin || day > fDayMax) {
|
|
return NULL; // don't have it.
|
|
}
|
|
|
|
// Linear search the held strings
|
|
for(int n=0;n<fDatesLen;n++) {
|
|
if(fDates[n].offset == day) {
|
|
len = fDates[n].len;
|
|
return fDates[n].string;
|
|
}
|
|
}
|
|
|
|
return NULL; // not found.
|
|
}
|
|
|
|
UnicodeString&
|
|
RelativeDateFormat::toPattern(UnicodeString& result, UErrorCode& status) const
|
|
{
|
|
if (!U_FAILURE(status)) {
|
|
result.remove();
|
|
if (fTimeFormat == NULL || fCombinedFormat == 0) {
|
|
if (fDateFormat != NULL) {
|
|
UnicodeString datePattern;
|
|
this->toPatternDate(datePattern, status);
|
|
if (!U_FAILURE(status)) {
|
|
result.setTo(datePattern);
|
|
}
|
|
}
|
|
} else {
|
|
UnicodeString datePattern, timePattern;
|
|
this->toPatternDate(datePattern, status);
|
|
this->toPatternTime(timePattern, status);
|
|
if (!U_FAILURE(status)) {
|
|
Formattable timeDatePatterns[] = { timePattern, datePattern };
|
|
FieldPosition pos;
|
|
fCombinedFormat->format(timeDatePatterns, 2, result, pos, status);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
RelativeDateFormat::toPatternDate(UnicodeString& result, UErrorCode& status) const
|
|
{
|
|
if (!U_FAILURE(status)) {
|
|
result.remove();
|
|
if ( fDateFormat ) {
|
|
if ( fDateFormat->getDynamicClassID()==SimpleDateFormat::getStaticClassID() ) {
|
|
((SimpleDateFormat*)fDateFormat)->toPattern(result);
|
|
} else {
|
|
status = U_UNSUPPORTED_ERROR;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) const
|
|
{
|
|
if (!U_FAILURE(status)) {
|
|
result.remove();
|
|
if ( fTimeFormat ) {
|
|
if ( fTimeFormat->getDynamicClassID()==SimpleDateFormat::getStaticClassID() ) {
|
|
((SimpleDateFormat*)fTimeFormat)->toPattern(result);
|
|
} else {
|
|
status = U_UNSUPPORTED_ERROR;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status)
|
|
{
|
|
if (!U_FAILURE(status)) {
|
|
if ( fDateFormat && fDateFormat->getDynamicClassID()!=SimpleDateFormat::getStaticClassID() ) {
|
|
status = U_UNSUPPORTED_ERROR;
|
|
return;
|
|
}
|
|
if ( fTimeFormat && fTimeFormat->getDynamicClassID()!=SimpleDateFormat::getStaticClassID() ) {
|
|
status = U_UNSUPPORTED_ERROR;
|
|
return;
|
|
}
|
|
if ( fDateFormat ) {
|
|
((SimpleDateFormat*)fDateFormat)->applyPattern(datePattern);
|
|
}
|
|
if ( fTimeFormat ) {
|
|
((SimpleDateFormat*)fTimeFormat)->applyPattern(timePattern);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RelativeDateFormat::loadDates(UErrorCode &status) {
|
|
CalendarData calData(fLocale, "gregorian", status);
|
|
|
|
UErrorCode tempStatus = status;
|
|
UResourceBundle *dateTimePatterns = calData.getByKey(DT_DateTimePatternsTag, tempStatus);
|
|
if(U_SUCCESS(tempStatus)) {
|
|
int32_t patternsSize = ures_getSize(dateTimePatterns);
|
|
if (patternsSize > kDateTime) {
|
|
int32_t resStrLen = 0;
|
|
|
|
int32_t glueIndex = kDateTime;
|
|
if (patternsSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) {
|
|
// Get proper date time format
|
|
switch (fDateStyle) {
|
|
case kFullRelative:
|
|
case kFull:
|
|
glueIndex = kDateTimeOffset + kFull;
|
|
break;
|
|
case kLongRelative:
|
|
case kLong:
|
|
glueIndex = kDateTimeOffset + kLong;
|
|
break;
|
|
case kMediumRelative:
|
|
case kMedium:
|
|
glueIndex = kDateTimeOffset + kMedium;
|
|
break;
|
|
case kShortRelative:
|
|
case kShort:
|
|
glueIndex = kDateTimeOffset + kShort;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const UChar *resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &tempStatus);
|
|
fCombinedFormat = new MessageFormat(UnicodeString(TRUE, resStr, resStrLen), fLocale, tempStatus);
|
|
}
|
|
}
|
|
|
|
UResourceBundle *strings = calData.getByKey3("fields", "day", "relative", status);
|
|
// set up min/max
|
|
fDayMin=-1;
|
|
fDayMax=1;
|
|
|
|
if(U_FAILURE(status)) {
|
|
fDatesLen=0;
|
|
return;
|
|
}
|
|
|
|
fDatesLen = ures_getSize(strings);
|
|
fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen);
|
|
|
|
// Load in each item into the array...
|
|
int n = 0;
|
|
|
|
UResourceBundle *subString = NULL;
|
|
|
|
while(ures_hasNext(strings) && U_SUCCESS(status)) { // iterate over items
|
|
subString = ures_getNextResource(strings, subString, &status);
|
|
|
|
if(U_FAILURE(status) || (subString==NULL)) break;
|
|
|
|
// key = offset #
|
|
const char *key = ures_getKey(subString);
|
|
|
|
// load the string and length
|
|
int32_t aLen;
|
|
const UChar* aString = ures_getString(subString, &aLen, &status);
|
|
|
|
if(U_FAILURE(status) || aString == NULL) break;
|
|
|
|
// calculate the offset
|
|
int32_t offset = atoi(key);
|
|
|
|
// set min/max
|
|
if(offset < fDayMin) {
|
|
fDayMin = offset;
|
|
}
|
|
if(offset > fDayMax) {
|
|
fDayMax = offset;
|
|
}
|
|
|
|
// copy the string pointer
|
|
fDates[n].offset = offset;
|
|
fDates[n].string = aString;
|
|
fDates[n].len = aLen;
|
|
|
|
n++;
|
|
}
|
|
ures_close(subString);
|
|
|
|
// the fDates[] array could be sorted here, for direct access.
|
|
}
|
|
|
|
|
|
// this should to be in DateFormat, instead it was copied from SimpleDateFormat.
|
|
|
|
Calendar*
|
|
RelativeDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status)
|
|
{
|
|
if(!U_FAILURE(status)) {
|
|
fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status);
|
|
}
|
|
if (U_SUCCESS(status) && fCalendar == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return fCalendar;
|
|
}
|
|
|
|
int32_t RelativeDateFormat::dayDifference(Calendar &cal, UErrorCode &status) {
|
|
if(U_FAILURE(status)) {
|
|
return 0;
|
|
}
|
|
// TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
|
|
Calendar *nowCal = cal.clone();
|
|
nowCal->setTime(Calendar::getNow(), status);
|
|
|
|
// For the day difference, we are interested in the difference in the (modified) julian day number
|
|
// which is midnight to midnight. Using fieldDifference() is NOT correct here, because
|
|
// 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow".
|
|
int32_t dayDiff = cal.get(UCAL_JULIAN_DAY, status) - nowCal->get(UCAL_JULIAN_DAY, status);
|
|
|
|
delete nowCal;
|
|
return dayDiff;
|
|
}
|
|
|
|
U_NAMESPACE_END
|
|
|
|
#endif
|
|
|