ICU-11706 (and #11726) Fix DateIntervalFormat handling of (1) skeletons with seconds, (2) FieldPosition

X-SVN-Rev: 37613
This commit is contained in:
Peter Edberg 2015-06-23 22:07:03 +00:00
parent 15ee90b9a3
commit a8325d335c
5 changed files with 404 additions and 56 deletions

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (C) 2008-2014, International Business Machines Corporation and
* Copyright (C) 2008-2015, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*
@ -117,7 +117,10 @@ DateIntervalFormat::DateIntervalFormat()
fDateFormat(NULL),
fFromCalendar(NULL),
fToCalendar(NULL),
fDtpng(NULL)
fDtpng(NULL),
fDatePattern(NULL),
fTimePattern(NULL),
fDateTimeFormat(NULL)
{}
@ -127,7 +130,10 @@ DateIntervalFormat::DateIntervalFormat(const DateIntervalFormat& itvfmt)
fDateFormat(NULL),
fFromCalendar(NULL),
fToCalendar(NULL),
fDtpng(NULL) {
fDtpng(NULL),
fDatePattern(NULL),
fTimePattern(NULL),
fDateTimeFormat(NULL) {
*this = itvfmt;
}
@ -140,6 +146,9 @@ DateIntervalFormat::operator=(const DateIntervalFormat& itvfmt) {
delete fFromCalendar;
delete fToCalendar;
delete fDtpng;
delete fDatePattern;
delete fTimePattern;
delete fDateTimeFormat;
if ( itvfmt.fDateFormat ) {
fDateFormat = (SimpleDateFormat*)itvfmt.fDateFormat->clone();
} else {
@ -167,7 +176,12 @@ DateIntervalFormat::operator=(const DateIntervalFormat& itvfmt) {
}
if (itvfmt.fDtpng) {
fDtpng = itvfmt.fDtpng->clone();
} else {
fDtpng = NULL;
}
fDatePattern = (itvfmt.fDatePattern)? (UnicodeString*)itvfmt.fDatePattern->clone(): NULL;
fTimePattern = (itvfmt.fTimePattern)? (UnicodeString*)itvfmt.fTimePattern->clone(): NULL;
fDateTimeFormat = (itvfmt.fDateTimeFormat)? (UnicodeString*)itvfmt.fDateTimeFormat->clone(): NULL;
}
return *this;
}
@ -179,6 +193,9 @@ DateIntervalFormat::~DateIntervalFormat() {
delete fFromCalendar;
delete fToCalendar;
delete fDtpng;
delete fDatePattern;
delete fTimePattern;
delete fDateTimeFormat;
}
@ -201,6 +218,9 @@ DateIntervalFormat::operator==(const Format& other) const {
equal = fFromCalendar->isEquivalentTo(*fmt->fFromCalendar) ;
equal = fToCalendar->isEquivalentTo(*fmt->fToCalendar) ;
equal = (fSkeleton == fmt->fSkeleton);
equal = ((fDatePattern == NULL && fmt->fDatePattern == NULL) || (fDatePattern && fmt->fDatePattern && *fDatePattern == *fmt->fDatePattern));
equal = ((fTimePattern == NULL && fmt->fTimePattern == NULL) || (fTimePattern && fmt->fTimePattern && *fTimePattern == *fmt->fTimePattern));
equal = ((fDateTimeFormat == NULL && fmt->fDateTimeFormat == NULL) || (fDateTimeFormat && fmt->fDateTimeFormat && *fDateTimeFormat == *fmt->fDateTimeFormat));
#endif
UBool res;
res = ( this == fmt ) ||
@ -214,6 +234,9 @@ DateIntervalFormat::operator==(const Format& other) const {
fToCalendar &&
fToCalendar->isEquivalentTo(*fmt->fToCalendar) &&
fSkeleton == fmt->fSkeleton &&
((fDatePattern == NULL && fmt->fDatePattern == NULL) || (fDatePattern && fmt->fDatePattern && *fDatePattern == *fmt->fDatePattern)) &&
((fTimePattern == NULL && fmt->fTimePattern == NULL) || (fTimePattern && fmt->fTimePattern && *fTimePattern == *fmt->fTimePattern)) &&
((fDateTimeFormat == NULL && fmt->fDateTimeFormat == NULL) || (fDateTimeFormat && fmt->fDateTimeFormat && *fDateTimeFormat == *fmt->fDateTimeFormat)) &&
fDtpng &&
(*fDtpng == *fmt->fDtpng) );
int8_t i;
@ -314,17 +337,21 @@ DateIntervalFormat::format(Calendar& fromCalendar,
} else if ( fromCalendar.get(UCAL_MINUTE, status) !=
toCalendar.get(UCAL_MINUTE, status) ) {
field = UCAL_MINUTE;
} else if ( fromCalendar.get(UCAL_SECOND, status) !=
toCalendar.get(UCAL_SECOND, status) ) {
field = UCAL_SECOND;
}
if ( U_FAILURE(status) ) {
return appendTo;
}
if ( field == UCAL_FIELD_COUNT ) {
/* ignore the second/millisecond etc. small fields' difference.
/* ignore the millisecond etc. small fields' difference.
* use single date when all the above are the same.
*/
return fDateFormat->format(fromCalendar, appendTo, pos);
}
UBool fromToOnSameDay = (field==UCAL_AM_PM || field==UCAL_HOUR || field==UCAL_MINUTE || field==UCAL_SECOND);
// following call should not set wrong status,
// all the pass-in fields are valid till here
@ -341,7 +368,7 @@ DateIntervalFormat::format(Calendar& fromCalendar,
*/
return fDateFormat->format(fromCalendar, appendTo, pos);
}
return fallbackFormat(fromCalendar, toCalendar, appendTo, pos, status);
return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, status);
}
// If the first part in interval pattern is empty,
// the 2nd part of it saves the full-pattern used in fall-back.
@ -351,7 +378,7 @@ DateIntervalFormat::format(Calendar& fromCalendar,
UnicodeString originalPattern;
fDateFormat->toPattern(originalPattern);
fDateFormat->applyPattern(intervalPattern.secondPart);
appendTo = fallbackFormat(fromCalendar, toCalendar, appendTo, pos, status);
appendTo = fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, status);
fDateFormat->applyPattern(originalPattern);
return appendTo;
}
@ -372,7 +399,12 @@ DateIntervalFormat::format(Calendar& fromCalendar,
fDateFormat->format(*firstCal, appendTo, pos);
if ( !intervalPattern.secondPart.isEmpty() ) {
fDateFormat->applyPattern(intervalPattern.secondPart);
fDateFormat->format(*secondCal, appendTo, pos);
FieldPosition otherPos;
otherPos.setField(pos.getField());
fDateFormat->format(*secondCal, appendTo, otherPos);
if (pos.getEndIndex() == 0 && otherPos.getEndIndex() > 0) {
pos = otherPos;
}
}
fDateFormat->applyPattern(originalPattern);
return appendTo;
@ -468,7 +500,10 @@ DateIntervalFormat::DateIntervalFormat(const Locale& locale,
fDateFormat(NULL),
fFromCalendar(NULL),
fToCalendar(NULL),
fDtpng(NULL)
fDtpng(NULL),
fDatePattern(NULL),
fTimePattern(NULL),
fDateTimeFormat(NULL)
{
if ( U_FAILURE(status) ) {
delete dtItvInfo;
@ -636,10 +671,39 @@ DateIntervalFormat::initializePattern(UErrorCode& status) {
PRINTMESG(mesg)
#endif
// move this up here since we need it for fallbacks
if ( timeSkeleton.length() > 0 && dateSkeleton.length() > 0 ) {
// Need the Date/Time pattern for concatenation of the date
// with the time interval.
// The date/time pattern ( such as {0} {1} ) is saved in
// calendar, that is why need to get the CalendarData here.
CalendarData* calData = new CalendarData(locale, NULL, status);
if ( U_FAILURE(status) ) {
delete calData;
return;
}
if ( calData == NULL ) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
const UResourceBundle* dateTimePatternsRes = calData->getByKey(
gDateTimePatternsTag, status);
int32_t dateTimeFormatLength;
const UChar* dateTimeFormat = ures_getStringByIndex(
dateTimePatternsRes,
(int32_t)DateFormat::kDateTime,
&dateTimeFormatLength, &status);
if ( U_SUCCESS(status) && dateTimeFormatLength >= 3 ) {
fDateTimeFormat = new UnicodeString(dateTimeFormat, dateTimeFormatLength);
}
delete calData;
}
UBool found = setSeparateDateTimePtn(normalizedDateSkeleton,
normalizedTimeSkeleton);
// for skeletons with seconds, found is false and we enter this block
if ( found == false ) {
// use fallback
// TODO: if user asks "m"(minute), but "d"(day) differ
@ -716,42 +780,17 @@ DateIntervalFormat::initializePattern(UErrorCode& status) {
* 2) otherwise, present the date followed by the
* range expression for the time.
*/
// Need the Date/Time pattern for concatnation the date with
// the time interval.
// The date/time pattern ( such as {0} {1} ) is saved in
// calendar, that is why need to get the CalendarData here.
CalendarData* calData = new CalendarData(locale, NULL, status);
if ( U_FAILURE(status) ) {
delete calData;
return;
}
if ( calData == NULL ) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
const UResourceBundle* dateTimePatternsRes = calData->getByKey(
gDateTimePatternsTag, status);
int32_t dateTimeFormatLength;
const UChar* dateTimeFormat = ures_getStringByIndex(
dateTimePatternsRes,
(int32_t)DateFormat::kDateTime,
&dateTimeFormatLength, &status);
if ( U_FAILURE(status) ) {
if ( fDateTimeFormat == 0 ) {
// earlier failure getting dateTimeFormat
return;
}
UnicodeString datePattern = fDtpng->getBestPattern(dateSkeleton, status);
concatSingleDate2TimeInterval(dateTimeFormat, dateTimeFormatLength,
datePattern, UCAL_AM_PM, status);
concatSingleDate2TimeInterval(dateTimeFormat, dateTimeFormatLength,
datePattern, UCAL_HOUR, status);
concatSingleDate2TimeInterval(dateTimeFormat, dateTimeFormatLength,
datePattern, UCAL_MINUTE, status);
delete calData;
concatSingleDate2TimeInterval(*fDateTimeFormat, datePattern, UCAL_AM_PM, status);
concatSingleDate2TimeInterval(*fDateTimeFormat, datePattern, UCAL_HOUR, status);
concatSingleDate2TimeInterval(*fDateTimeFormat, datePattern, UCAL_MINUTE, status);
}
}
@ -966,11 +1005,25 @@ DateIntervalFormat::setSeparateDateTimePtn(
return false;
}
// Set patterns for fallback use, need to do this
// before returning if differenceInfo == -1
UErrorCode status;
if ( dateSkeleton.length() != 0 && fDtpng != NULL ) {
status = U_ZERO_ERROR;
fDatePattern = new UnicodeString(fDtpng->getBestPattern(dateSkeleton, status));
}
if ( timeSkeleton.length() != 0 && fDtpng != NULL ) {
status = U_ZERO_ERROR;
fTimePattern = new UnicodeString(fDtpng->getBestPattern(timeSkeleton, status));
}
// difference:
// 0 means the best matched skeleton is the same as input skeleton
// 1 means the fields are the same, but field width are different
// 2 means the only difference between fields are v/z,
// -1 means there are other fields difference
// (this will happen, for instance, if the supplied skeleton has seconds,
// but no skeletons in the intervalFormats data do)
if ( differenceInfo == -1 ) {
// skeleton has different fields, not only v/z difference
return false;
@ -1264,33 +1317,98 @@ DateIntervalFormat::splitPatternInto2Part(const UnicodeString& intervalPattern)
return (i - count);
}
static const UChar bracketedZero[] = {0x7B,0x30,0x7D};
static const UChar bracketedOne[] = {0x7B,0x31,0x7D};
void
DateIntervalFormat::adjustPosition(UnicodeString& combiningPattern, // has {0} and {1} in it
UnicodeString& pat0, FieldPosition& pos0, // pattern and pos corresponding to {0}
UnicodeString& pat1, FieldPosition& pos1, // pattern and pos corresponding to {1}
FieldPosition& posResult) {
int32_t index0 = combiningPattern.indexOf(bracketedZero, 3, 0);
int32_t index1 = combiningPattern.indexOf(bracketedOne, 3, 0);
if (index0 < 0 || index1 < 0) {
return;
}
if (index0 < index1) {
if (pos0.getEndIndex() > 0) {
posResult.setBeginIndex(pos0.getBeginIndex() + index0);
posResult.setEndIndex(pos0.getEndIndex() + index0);
} else if (pos1.getEndIndex() > 0) {
// here index1 >= 3
index1 += pat0.length() - 3; // adjust for pat0 replacing {0}
posResult.setBeginIndex(pos1.getBeginIndex() + index1);
posResult.setEndIndex(pos1.getEndIndex() + index1);
}
} else {
if (pos1.getEndIndex() > 0) {
posResult.setBeginIndex(pos1.getBeginIndex() + index1);
posResult.setEndIndex(pos1.getEndIndex() + index1);
} else if (pos0.getEndIndex() > 0) {
// here index0 >= 3
index0 += pat1.length() - 3; // adjust for pat1 replacing {1}
posResult.setBeginIndex(pos0.getBeginIndex() + index0);
posResult.setEndIndex(pos0.getEndIndex() + index0);
}
}
}
UnicodeString&
DateIntervalFormat::fallbackFormat(Calendar& fromCalendar,
Calendar& toCalendar,
UBool fromToOnSameDay, // new
UnicodeString& appendTo,
FieldPosition& pos,
UErrorCode& status) const {
if ( U_FAILURE(status) ) {
return appendTo;
}
UnicodeString fullPattern; // for saving the pattern in fDateFormat
UBool formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern && fTimePattern);
// the fall back
// no need delete earlierDate and laterDate since they are adopted
if (formatDatePlusTimeRange) {
fDateFormat->toPattern(fullPattern); // save current pattern, restore later
fDateFormat->applyPattern(*fTimePattern);
}
FieldPosition otherPos;
otherPos.setField(pos.getField());
UnicodeString* earlierDate = new UnicodeString();
*earlierDate = fDateFormat->format(fromCalendar, *earlierDate, pos);
fDateFormat->format(fromCalendar, *earlierDate, pos);
UnicodeString* laterDate = new UnicodeString();
*laterDate = fDateFormat->format(toCalendar, *laterDate, pos);
fDateFormat->format(toCalendar, *laterDate, otherPos);
UnicodeString fallbackPattern;
fInfo->getFallbackIntervalPattern(fallbackPattern);
adjustPosition(fallbackPattern, *earlierDate, pos, *laterDate, otherPos, pos);
Formattable fmtArray[2];
fmtArray[0].adoptString(earlierDate);
fmtArray[1].adoptString(laterDate);
UnicodeString fallback;
MessageFormat::format(fallbackPattern, fmtArray, 2, fallback, status);
UnicodeString fallbackRange;
MessageFormat::format(fallbackPattern, fmtArray, 2, fallbackRange, status);
if ( U_SUCCESS(status) ) {
appendTo.append(fallback);
if (!formatDatePlusTimeRange) {
appendTo.append(fallbackRange);
} else {
// fallbackRange has just the time range, need to format the date part and combine that
fDateFormat->applyPattern(*fDatePattern);
UnicodeString* datePortion = new UnicodeString();
otherPos.setBeginIndex(0);
otherPos.setEndIndex(0);
fDateFormat->format(fromCalendar, *datePortion, otherPos);
adjustPosition(*fDateTimeFormat, fallbackRange, pos, *datePortion, otherPos, pos);
fmtArray[0].setString(fallbackRange); // {0} is time range
fmtArray[1].adoptString(datePortion); // {1} is single date portion
fallbackRange.remove();
MessageFormat::format(*fDateTimeFormat, fmtArray, 2, fallbackRange, status);
if ( U_SUCCESS(status) ) {
appendTo.append(fallbackRange);
}
}
}
if (formatDatePlusTimeRange) {
// restore full pattern
fDateFormat->applyPattern(fullPattern);
}
return appendTo;
}
@ -1420,8 +1538,7 @@ DateIntervalFormat::adjustFieldWidth(const UnicodeString& inputSkeleton,
void
DateIntervalFormat::concatSingleDate2TimeInterval(const UChar* format,
int32_t formatLen,
DateIntervalFormat::concatSingleDate2TimeInterval(UnicodeString& format,
const UnicodeString& datePattern,
UCalendarDateFields field,
UErrorCode& status) {
@ -1441,8 +1558,7 @@ DateIntervalFormat::concatSingleDate2TimeInterval(const UChar* format,
fmtArray[0].adoptString(timeIntervalPattern);
fmtArray[1].adoptString(dateStr);
UnicodeString combinedPattern;
MessageFormat::format(UnicodeString(TRUE, format, formatLen),
fmtArray, 2, combinedPattern, status);
MessageFormat::format(format, fmtArray, 2, combinedPattern, status);
if ( U_FAILURE(status) ) {
return;
}

View file

@ -574,6 +574,9 @@ DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field,
case UCAL_MINUTE:
index = kIPI_MINUTE;
break;
case UCAL_SECOND:
index = kIPI_SECOND;
break;
default:
status = U_ILLEGAL_ARGUMENT_ERROR;
}

View file

@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2008-2013, International Business Machines Corporation and
* Copyright (C) 2008-2013,2015, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*
@ -93,9 +93,11 @@ U_NAMESPACE_BEGIN
*
* <P>
* The calendar fields we support for interval formatting are:
* year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
* year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and second
* (though we do not currently have specific intervalFormat date for skeletons
* with seconds).
* Those calendar fields can be defined in the following order:
* year > month > date > hour (in day) > minute
* year > month > date > hour (in day) > minute > second
*
* The largest different calendar fields between 2 calendars is the
* first different calendar field in above order.
@ -664,6 +666,21 @@ private:
* Below are for generating interval patterns local to the formatter
*/
/**
* @param combiningPattern xxx
* @param pat0 xxx
* @param pos0 xxx
* @param pat1 xxx
* @param pos1 xxx
* @param posResult xxx
*/
static void
adjustPosition(UnicodeString& combiningPattern, // has {0} and {1} in it
UnicodeString& pat0, FieldPosition& pos0, // pattern and pos corresponding to {0}
UnicodeString& pat1, FieldPosition& pos1, // pattern and pos corresponding to {1}
FieldPosition& posResult);
/**
* Format 2 Calendars using fall-back interval pattern
@ -675,6 +692,8 @@ private:
* to be formatted into date interval string
* @param toCalendar calendar set to the to date in date interval
* to be formatted into date interval string
* @param fromToOnSameDay TRUE iff from and to dates are on the same day
* (any difference is in ampm/hours or below)
* @param appendTo Output parameter to receive result.
* Result is appended to existing contents.
* @param pos On input: an alignment field, if desired.
@ -684,6 +703,7 @@ private:
*/
UnicodeString& fallbackFormat(Calendar& fromCalendar,
Calendar& toCalendar,
UBool fromToOnSameDay,
UnicodeString& appendTo,
FieldPosition& pos,
UErrorCode& status) const;
@ -874,13 +894,11 @@ private:
* both time and date. Present the date followed by
* the range expression for the time.
* @param format date and time format
* @param formatLen format string length
* @param datePattern date pattern
* @param field time calendar field: AM_PM, HOUR, MINUTE
* @param status output param set to success/failure code on exit
*/
void concatSingleDate2TimeInterval(const UChar* format,
int32_t formatLen,
void concatSingleDate2TimeInterval(UnicodeString& format,
const UnicodeString& datePattern,
UCalendarDateFields field,
UErrorCode& status);
@ -966,10 +984,17 @@ private:
DateTimePatternGenerator* fDtpng;
/**
* Following are interval information relavent (locale) to this formatter.
* Following are interval information relevant (locale) to this formatter.
*/
UnicodeString fSkeleton;
PatternInfo fIntervalPatterns[DateIntervalInfo::kIPI_MAX_INDEX];
/**
* Patterns for fallback formatting.
*/
UnicodeString* fDatePattern;
UnicodeString* fTimePattern;
UnicodeString* fDateTimeFormat;
};
inline UBool

View file

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 2008-2014, International Business Machines Corporation and
* Copyright (C) 2008-2015, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*
@ -353,6 +353,7 @@ private:
kIPI_AM_PM,
kIPI_HOUR,
kIPI_MINUTE,
kIPI_SECOND,
kIPI_MAX_INDEX
};
public:

View file

@ -1,5 +1,5 @@
/********************************************************************
* Copyright (c) 2011-2014, International Business Machines Corporation
* Copyright (c) 2011-2015, International Business Machines Corporation
* and others. All Rights Reserved.
********************************************************************/
/* C API TEST FOR DATE INTERVAL FORMAT */
@ -16,6 +16,7 @@
#include "cmemory.h"
static void TestDateIntervalFormat(void);
static void TestFPos_SkelWithSeconds(void);
#define LEN(a) (sizeof(a)/sizeof(a[0]))
@ -26,6 +27,7 @@ void addDateIntervalFormatTest(TestNode** root);
void addDateIntervalFormatTest(TestNode** root)
{
TESTCASE(TestDateIntervalFormat);
TESTCASE(TestFPos_SkelWithSeconds);
}
static const char tzUSPacific[] = "US/Pacific";
@ -114,4 +116,205 @@ static void TestDateIntervalFormat()
ctest_resetTimeZone();
}
/********************************************************************
* TestFPos_SkelWithSeconds and related data
********************************************************************
*/
static UChar zoneGMT[] = { 0x47,0x4D,0x54,0 }; // GMT
static const UDate startTime = 1416474000000.0; // 2014 Nov 20 09:00 GMT
static const double deltas[] = {
0.0, // none
200.0, // 200 millisec
20000.0, // 20 sec
1200000.0, // 20 min
7200000.0, // 2 hrs
43200000.0, // 12 hrs
691200000.0, // 8 days
1382400000.0, // 16 days,
8640000000.0, // 100 days
-1.0
};
enum { kNumDeltas = sizeof(deltas)/sizeof(deltas[0]) - 1 };
typedef struct {
int32_t posBegin;
int32_t posEnd;
const char * format;
} ExpectPosAndFormat;
static const ExpectPosAndFormat exp_en_HHmm[kNumDeltas] = {
{ 3, 5, "09:00" },
{ 3, 5, "09:00" },
{ 3, 5, "09:00" },
{ 3, 5, "09:00 \\u2013 09:20" },
{ 3, 5, "09:00 \\u2013 11:00" },
{ 3, 5, "09:00 \\u2013 21:00" },
{ 15, 17, "11/20/2014, 09:00 \\u2013 11/28/2014, 09:00" },
{ 15, 17, "11/20/2014, 09:00 \\u2013 12/6/2014, 09:00" },
{ 15, 17, "11/20/2014, 09:00 \\u2013 2/28/2015, 09:00" }
};
static const ExpectPosAndFormat exp_en_HHmmss[kNumDeltas] = {
{ 3, 5, "09:00:00" },
{ 3, 5, "09:00:00" },
{ 3, 5, "09:00:00 \\u2013 09:00:20" },
{ 3, 5, "09:00:00 \\u2013 09:20:00" },
{ 3, 5, "09:00:00 \\u2013 11:00:00" },
{ 3, 5, "09:00:00 \\u2013 21:00:00" },
{ 15, 17, "11/20/2014, 09:00:00 \\u2013 11/28/2014, 09:00:00" },
{ 15, 17, "11/20/2014, 09:00:00 \\u2013 12/6/2014, 09:00:00" },
{ 15, 17, "11/20/2014, 09:00:00 \\u2013 2/28/2015, 09:00:00" }
};
static const ExpectPosAndFormat exp_en_yyMMdd[kNumDeltas] = {
{ 0, 0, "11/20/14" },
{ 0, 0, "11/20/14" },
{ 0, 0, "11/20/14" },
{ 0, 0, "11/20/14" },
{ 0, 0, "11/20/14" },
{ 0, 0, "11/20/14" },
{ 0, 0, "11/20/14 \\u2013 11/28/14" },
{ 0, 0, "11/20/14 \\u2013 12/6/14" },
{ 0, 0, "11/20/14 \\u2013 2/28/15" }
};
static const ExpectPosAndFormat exp_en_yyMMddHHmm[kNumDeltas] = {
{ 13, 15, "11/20/14, 09:00" },
{ 13, 15, "11/20/14, 09:00" },
{ 13, 15, "11/20/14, 09:00" },
{ 13, 15, "11/20/14, 09:00 \\u2013 09:20" },
{ 13, 15, "11/20/14, 09:00 \\u2013 11:00" },
{ 13, 15, "11/20/14, 09:00 \\u2013 21:00" },
{ 13, 15, "11/20/14, 09:00 \\u2013 11/28/14, 09:00" },
{ 13, 15, "11/20/14, 09:00 \\u2013 12/06/14, 09:00" },
{ 13, 15, "11/20/14, 09:00 \\u2013 02/28/15, 09:00" }
};
static const ExpectPosAndFormat exp_en_yyMMddHHmmss[kNumDeltas] = {
{ 13, 15, "11/20/14, 09:00:00" },
{ 13, 15, "11/20/14, 09:00:00" },
{ 13, 15, "11/20/14, 09:00:00 \\u2013 09:00:20" },
{ 13, 15, "11/20/14, 09:00:00 \\u2013 09:20:00" },
{ 13, 15, "11/20/14, 09:00:00 \\u2013 11:00:00" },
{ 13, 15, "11/20/14, 09:00:00 \\u2013 21:00:00" },
{ 13, 15, "11/20/14, 09:00:00 \\u2013 11/28/14, 09:00:00" },
{ 13, 15, "11/20/14, 09:00:00 \\u2013 12/06/14, 09:00:00" },
{ 13, 15, "11/20/14, 09:00:00 \\u2013 02/28/15, 09:00:00" }
};
static const ExpectPosAndFormat exp_en_yMMMdhmmssz[kNumDeltas] = {
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \\u2013 9:00:20 AM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \\u2013 9:20:00 AM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \\u2013 11:00:00 AM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \\u2013 9:00:00 PM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \\u2013 Nov 28, 2014, 9:00:00 AM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \\u2013 Dec 6, 2014, 9:00:00 AM GMT" },
{ 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \\u2013 Feb 28, 2015, 9:00:00 AM GMT" }
};
static const ExpectPosAndFormat exp_ja_yyMMddHHmm[kNumDeltas] = {
{ 11, 13, "14/11/20 9:00" },
{ 11, 13, "14/11/20 9:00" },
{ 11, 13, "14/11/20 9:00" },
{ 11, 13, "14/11/20 9\\u664200\\u5206\\uFF5E9\\u664220\\u5206" },
{ 11, 13, "14/11/20 9\\u664200\\u5206\\uFF5E11\\u664200\\u5206" },
{ 11, 13, "14/11/20 9\\u664200\\u5206\\uFF5E21\\u664200\\u5206" },
{ 11, 13, "14/11/20 9:00\\uFF5E14/11/28 9:00" },
{ 11, 13, "14/11/20 9:00\\uFF5E14/12/06 9:00" },
{ 11, 13, "14/11/20 9:00\\uFF5E15/02/28 9:00" }
};
static const ExpectPosAndFormat exp_ja_yyMMddHHmmss[kNumDeltas] = {
{ 11, 13, "14/11/20 9:00:00" },
{ 11, 13, "14/11/20 9:00:00" },
{ 11, 13, "14/11/20 9:00:00\\uFF5E9:00:20" },
{ 11, 13, "14/11/20 9:00:00\\uFF5E9:20:00" },
{ 11, 13, "14/11/20 9:00:00\\uFF5E11:00:00" },
{ 11, 13, "14/11/20 9:00:00\\uFF5E21:00:00" },
{ 11, 13, "14/11/20 9:00:00\\uFF5E14/11/28 9:00:00" },
{ 11, 13, "14/11/20 9:00:00\\uFF5E14/12/06 9:00:00" },
{ 11, 13, "14/11/20 9:00:00\\uFF5E15/02/28 9:00:00" }
};
static const ExpectPosAndFormat exp_ja_yMMMdHHmmss[kNumDeltas] = {
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00\\uFF5E9:00:20" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00\\uFF5E9:20:00" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00\\uFF5E11:00:00" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00\\uFF5E21:00:00" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00\\uFF5E2014\\u5E7411\\u670828\\u65E5 9:00:00" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00\\uFF5E2014\\u5E7412\\u67086\\u65E5 9:00:00" },
{ 14, 16, "2014\\u5E7411\\u670820\\u65E5 9:00:00\\uFF5E2015\\u5E742\\u670828\\u65E5 9:00:00" }
};
typedef struct {
const char * locale;
const char * skeleton;
UDateFormatField fieldToCheck;
const ExpectPosAndFormat * expected;
} LocaleAndSkeletonItem;
static const LocaleAndSkeletonItem locSkelItems[] = {
{ "en", "HHmm", UDAT_MINUTE_FIELD, exp_en_HHmm },
{ "en", "HHmmss", UDAT_MINUTE_FIELD, exp_en_HHmmss },
{ "en", "yyMMdd", UDAT_MINUTE_FIELD, exp_en_yyMMdd },
{ "en", "yyMMddHHmm", UDAT_MINUTE_FIELD, exp_en_yyMMddHHmm },
{ "en", "yyMMddHHmmss", UDAT_MINUTE_FIELD, exp_en_yyMMddHHmmss },
{ "en", "yMMMdhmmssz", UDAT_MINUTE_FIELD, exp_en_yMMMdhmmssz },
{ "ja", "yyMMddHHmm", UDAT_MINUTE_FIELD, exp_ja_yyMMddHHmm },
{ "ja", "yyMMddHHmmss", UDAT_MINUTE_FIELD, exp_ja_yyMMddHHmmss },
{ "ja", "yMMMdHHmmss", UDAT_MINUTE_FIELD, exp_ja_yMMMdHHmmss },
{ NULL, NULL, (UDateFormatField)0, NULL }
};
enum { kSizeUBuf = 96, kSizeBBuf = 192 };
static void TestFPos_SkelWithSeconds()
{
const LocaleAndSkeletonItem * locSkelItemPtr;
for (locSkelItemPtr = locSkelItems; locSkelItemPtr->locale != NULL; locSkelItemPtr++) {
UDateIntervalFormat* udifmt;
UChar ubuf[kSizeUBuf];
int32_t ulen, uelen;
UErrorCode status = U_ZERO_ERROR;
u_strFromUTF8(ubuf, kSizeUBuf, &ulen, locSkelItemPtr->skeleton, -1, &status);
udifmt = udtitvfmt_open(locSkelItemPtr->locale, ubuf, ulen, zoneGMT, -1, &status);
if ( U_FAILURE(status) ) {
log_data_err("FAIL: udtitvfmt_open for locale %s, skeleton %s: %s\n",
locSkelItemPtr->locale, locSkelItemPtr->skeleton, u_errorName(status));
} else {
const double * deltasPtr = deltas;
const ExpectPosAndFormat * expectedPtr = locSkelItemPtr->expected;
for (; *deltasPtr >= 0.0; deltasPtr++, expectedPtr++) {
UFieldPosition fpos = { locSkelItemPtr->fieldToCheck, 0, 0 };
UChar uebuf[kSizeUBuf];
char bbuf[kSizeBBuf];
char bebuf[kSizeBBuf];
status = U_ZERO_ERROR;
uelen = u_unescape(expectedPtr->format, uebuf, kSizeUBuf);
ulen = udtitvfmt_format(udifmt, startTime, startTime + *deltasPtr, ubuf, kSizeUBuf, &fpos, &status);
if ( U_FAILURE(status) ) {
log_err("FAIL: udtitvfmt_format for locale %s, skeleton %s, delta %.1f: %s\n",
locSkelItemPtr->locale, locSkelItemPtr->skeleton, *deltasPtr, u_errorName(status));
} else if ( ulen != uelen || u_strncmp(ubuf,uebuf,uelen) != 0 ||
fpos.beginIndex != expectedPtr->posBegin || fpos.endIndex != expectedPtr->posEnd ) {
u_strToUTF8(bbuf, kSizeBBuf, NULL, ubuf, ulen, &status);
u_strToUTF8(bebuf, kSizeBBuf, NULL, uebuf, uelen, &status); // convert back to get unescaped string
log_err("FAIL: udtitvfmt_format for locale %s, skeleton %s, delta %12.1f, expect %d-%d \"%s\", get %d-%d \"%s\"\n",
locSkelItemPtr->locale, locSkelItemPtr->skeleton, *deltasPtr,
expectedPtr->posBegin, expectedPtr->posEnd, bebuf,
fpos.beginIndex, fpos.endIndex, bbuf);
}
}
udtitvfmt_close(udifmt);
}
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */