]> granicus.if.org Git - icu/commitdiff
ICU-9656 Change RelativeDateFormat to keep 2 patterns (not formatters), and for fmt...
authorPeter Edberg <pedberg@unicode.org>
Mon, 15 Oct 2012 23:32:31 +0000 (23:32 +0000)
committerPeter Edberg <pedberg@unicode.org>
Mon, 15 Oct 2012 23:32:31 +0000 (23:32 +0000)
X-SVN-Rev: 32638

icu4c/source/i18n/reldtfmt.cpp
icu4c/source/i18n/reldtfmt.h
icu4c/source/test/cintltst/cdattst.c
icu4c/source/test/intltest/dadrfmt.cpp
icu4c/source/test/testdata/format.txt

index 065b0a1e612b9444af812f7144400a976489b873..d079ca5db1316f8e38ff63ce91a1b36faeb06b93 100644 (file)
@@ -1,6 +1,6 @@
 /*
 *******************************************************************************
-* Copyright (C) 2007-2011, International Business Machines Corporation and
+* Copyright (C) 2007-2012, International Business Machines Corporation and
 * others. All Rights Reserved.
 *******************************************************************************
 */
@@ -9,14 +9,10 @@
 
 #if !UCONFIG_NO_FORMATTING
 
-//#define DEBUG_RELDTFMT
-
-#include <stdio.h>
-#include <stdlib.h>
-
 #include "reldtfmt.h"
-#include "unicode/msgfmt.h"
+#include "unicode/datefmt.h"
 #include "unicode/smpdtfmt.h"
+#include "unicode/msgfmt.h"
 
 #include "gregoimp.h" // for CalendarData
 #include "cmemory.h"
@@ -39,50 +35,68 @@ 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)
+ DateFormat(other), fDateTimeFormatter(NULL), fDatePattern(other.fDatePattern),
+ fTimePattern(other.fTimePattern), fCombinedFormat(NULL),
+ fDateStyle(other.fDateStyle), 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(other.fDateTimeFormatter != NULL) {
+        fDateTimeFormatter = (SimpleDateFormat*)other.fDateTimeFormatter->clone();
+    }
+    if(other.fCombinedFormat != NULL) {
+        fCombinedFormat = (MessageFormat*)other.fCombinedFormat->clone();
     }
     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)
- {
+RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle,
+                                        const Locale& locale, UErrorCode& status) :
+ DateFormat(), fDateTimeFormatter(NULL), fDatePattern(), fTimePattern(), fCombinedFormat(NULL),
+ fDateStyle(dateStyle), 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) {
+    if (timeStyle < UDAT_NONE || timeStyle > UDAT_SHORT) {
         // don't support other time styles (e.g. relative styles), for now
         status = U_ILLEGAL_ARGUMENT_ERROR;
         return;
     }
+    UDateFormatStyle baseDateStyle = (dateStyle > UDAT_SHORT)? (UDateFormatStyle)(dateStyle & ~UDAT_RELATIVE): dateStyle;
+    DateFormat * df;
+    // Get fDateTimeFormatter from either date or time style (does not matter, we will override the pattern).
+    // We do need to get separate patterns for the date & time styles.
+    if (baseDateStyle != UDAT_NONE) {
+        df = createDateInstance((EStyle)baseDateStyle, locale);
+        fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df);
+        if (fDateTimeFormatter == NULL) {
+            status = U_UNSUPPORTED_ERROR;
+             return;
+        }
+        fDateTimeFormatter->toPattern(fDatePattern);
+        if (timeStyle != UDAT_NONE) {
+            df = createTimeInstance((EStyle)timeStyle, locale);
+            SimpleDateFormat *sdf = dynamic_cast<SimpleDateFormat *>(df);
+            if (sdf != NULL) {
+                sdf->toPattern(fTimePattern);
+                delete sdf;
+            }
+        }
+    } else {
+        // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormatter
+        df = createTimeInstance((EStyle)timeStyle, locale);
+        fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df);
+        if (fDateTimeFormatter == NULL) {
+            status = U_UNSUPPORTED_ERROR;
+            return;
+        }
+        fDateTimeFormatter->toPattern(fTimePattern);
+    }
     
     // Initialize the parent fCalendar, so that parse() works correctly.
     initializeCalendar(NULL, locale, status);
@@ -90,8 +104,7 @@ fDateStyle(dateStyle), fTimeStyle(timeStyle), fLocale(locale), fDatesLen(0), fDa
 }
 
 RelativeDateFormat::~RelativeDateFormat() {
-    delete fDateFormat;
-    delete fTimeFormat;
+    delete fDateTimeFormatter;
     delete fCombinedFormat;
     uprv_free(fDates);
 }
@@ -106,19 +119,21 @@ UBool RelativeDateFormat::operator==(const Format& other) const {
         // DateFormat::operator== guarantees following cast is safe
         RelativeDateFormat* that = (RelativeDateFormat*)&other;
         return (fDateStyle==that->fDateStyle   &&
-                fTimeStyle==that->fTimeStyle   &&
+                fDatePattern==that->fDatePattern   &&
+                fTimePattern==that->fTimePattern   &&
                 fLocale==that->fLocale);
     }
     return FALSE;
 }
 
+static const UChar APOSTROPHE = (UChar)0x0027;
+
 UnicodeString& RelativeDateFormat::format(  Calendar& cal,
                                 UnicodeString& appendTo,
                                 FieldPosition& pos) const {
                                 
     UErrorCode status = U_ZERO_ERROR;
-    UChar emptyStr = 0;
-    UnicodeString dateString(&emptyStr);
+    UnicodeString relativeDayString;
     
     // calculate the difference, in days, between 'cal' and now.
     int dayDiff = dayDifference(cal, status);
@@ -128,34 +143,35 @@ UnicodeString& RelativeDateFormat::format(  Calendar& cal,
     const UChar *theString = getStringForDay(dayDiff, len, status);
     if(U_SUCCESS(status) && (theString!=NULL)) {
         // found a relative string
-        dateString.setTo(theString, len);
+        relativeDayString.setTo(theString, len);
     }
     
-    if(fTimeFormat == NULL || fCombinedFormat == 0) {
-        if (dateString.length() > 0) {
-            appendTo.append(dateString);
-        } else if(fDateFormat != NULL) {
-            fDateFormat->format(cal,appendTo,pos);
+    if (fDatePattern.isEmpty()) {
+        fDateTimeFormatter->applyPattern(fTimePattern);
+        fDateTimeFormatter->format(cal,appendTo,pos);
+    } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) {
+        if (relativeDayString.length() > 0) {
+            appendTo.append(relativeDayString);
+        } else {
+            fDateTimeFormatter->applyPattern(fDatePattern);
+            fDateTimeFormatter->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 );
+        UnicodeString datePattern;
+        if (relativeDayString.length() > 0) {
+            // Need to quote the relativeDayString to make it a legal date pattern
+            relativeDayString.findAndReplace(UnicodeString("'"), UnicodeString("''") ); // double any existing APOSTROPHE
+            relativeDayString.insert(0, APOSTROPHE); // add APOSTROPHE at beginning...
+            relativeDayString.append(APOSTROPHE); // and at end
+            datePattern.setTo(relativeDayString);
+        } else {
+            datePattern.setTo(fDatePattern);
         }
+        UnicodeString combinedPattern;
+        Formattable timeDatePatterns[] = { fTimePattern, datePattern };
+        fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, pos, status); // pos is ignored by this
+        fDateTimeFormatter->applyPattern(combinedPattern);
+        fDateTimeFormatter->format(cal,appendTo,pos);
     }
     
     return appendTo;
@@ -181,40 +197,97 @@ 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
+    int32_t startIndex = pos.getIndex();
+    if (fDatePattern.isEmpty()) {
+        // no date pattern, try parsing as time
+        fDateTimeFormatter->applyPattern(fTimePattern);
+        fDateTimeFormatter->parse(text,cal,pos);
+    } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) {
+        // no time pattern or way to combine, try parsing as date
+        // first check whether text matches a relativeDayString
+        UBool matchedRelative = FALSE;
+        for (int n=0; n < fDatesLen && !matchedRelative; n++) {
+            if (fDates[n].string != NULL &&
+                    text.compare(startIndex, fDates[n].len, fDates[n].string) == 0) {
+                // it matched, handle the relative day string
+                UErrorCode status = U_ZERO_ERROR;
+                matchedRelative = TRUE;
+
+                // 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 field, set offset to beginning of rel day string
+                    pos.setErrorIndex(startIndex);
+                } else {
+                    pos.setIndex(startIndex + fDates[n].len);
+                }
+            }
         }
-    }
-    
-    // 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);
+        if (!matchedRelative) {
+            // just parse as normal date
+            fDateTimeFormatter->applyPattern(fDatePattern);
+            fDateTimeFormatter->parse(text,cal,pos);
+        }
+    } else {
+        // Here we replace any relativeDayString in text with the equivalent date
+        // formatted per fDatePattern, then parse text normally using the combined pattern.
+        UnicodeString modifiedText(text);
+        FieldPosition fPos;
+        int32_t dateStart = 0, origDateLen = 0, modDateLen = 0;
+        UErrorCode status = U_ZERO_ERROR;
+        for (int n=0; n < fDatesLen; n++) {
+            int32_t relativeStringOffset;
+            if (fDates[n].string != NULL &&
+                    (relativeStringOffset = modifiedText.indexOf(fDates[n].string, fDates[n].len, startIndex)) >= startIndex) {
+                // it matched, replace the relative date with a real one for parsing
+                UnicodeString dateString;
+                Calendar * tempCal = cal.clone();
+
+                // Set the calendar to now+offset
+                tempCal->setTime(Calendar::getNow(),status);
+                tempCal->add(UCAL_DATE,fDates[n].offset, status);
+                if(U_FAILURE(status)) { 
+                    pos.setErrorIndex(startIndex);
+                    delete tempCal;
+                    return;
+                }
+
+                fDateTimeFormatter->applyPattern(fDatePattern);
+                fDateTimeFormatter->format(*tempCal, dateString, fPos);
+                dateStart = relativeStringOffset;
+                origDateLen = fDates[n].len;
+                modDateLen = dateString.length();
+                modifiedText.replace(dateStart, origDateLen, dateString);
+                delete tempCal;
+                break;
             }
-            return;
+        }
+        UnicodeString combinedPattern;
+        Formattable timeDatePatterns[] = { fTimePattern, fDatePattern };
+        fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, fPos, status); // pos is ignored by this
+        fDateTimeFormatter->applyPattern(combinedPattern);
+        fDateTimeFormatter->parse(modifiedText,cal,pos);
+
+        // Adjust offsets
+        UBool noError = (pos.getErrorIndex() < 0);
+        int32_t offset = (noError)? pos.getIndex(): pos.getErrorIndex();
+        if (offset >= dateStart + modDateLen) {
+            // offset at or after the end of the replaced text,
+            // correct by the difference between original and replacement
+            offset -= (modDateLen - origDateLen);
+        } else if (offset >= dateStart) {
+            // offset in the replaced text, set it to the beginning of that text
+            // (i.e. the beginning of the relative day string)
+            offset = dateStart;
+        }
+        if (noError) {
+            pos.setIndex(offset);
+        } else {
+            pos.setErrorIndex(offset);
         }
     }
-    
-    // parse failed
 }
 
 UDate
@@ -260,23 +333,14 @@ 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);
-                }
-            }
+        if (fDatePattern.isEmpty()) {
+            result.setTo(fTimePattern);
+        } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) {
+            result.setTo(fDatePattern);
         } 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);
-            }
+            Formattable timeDatePatterns[] = { fTimePattern, fDatePattern };
+            FieldPosition pos;
+            fCombinedFormat->format(timeDatePatterns, 2, result, pos, status);
         }
     }
     return result;
@@ -287,14 +351,7 @@ RelativeDateFormat::toPatternDate(UnicodeString& result, UErrorCode& status) con
 {
     if (!U_FAILURE(status)) {
         result.remove();
-        if ( fDateFormat ) {
-            SimpleDateFormat* sdtfmt = dynamic_cast<SimpleDateFormat*>(fDateFormat);
-            if (sdtfmt != NULL) {
-                sdtfmt->toPattern(result);
-            } else {
-                status = U_UNSUPPORTED_ERROR;
-            }
-        }
+        result.setTo(fDatePattern);
     }
     return result;
 }
@@ -304,14 +361,7 @@ RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) con
 {
     if (!U_FAILURE(status)) {
         result.remove();
-        if ( fTimeFormat ) {
-            SimpleDateFormat* sdtfmt = dynamic_cast<SimpleDateFormat*>(fTimeFormat);
-            if (sdtfmt != NULL) {
-                sdtfmt->toPattern(result);
-            } else {
-                status = U_UNSUPPORTED_ERROR;
-            }
-        }
+        result.setTo(fTimePattern);
     }
     return result;
 }
@@ -320,33 +370,15 @@ void
 RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status)
 {
     if (!U_FAILURE(status)) {
-        SimpleDateFormat* sdtfmt = NULL;
-        SimpleDateFormat* stmfmt = NULL;
-        if (fDateFormat && (sdtfmt = dynamic_cast<SimpleDateFormat*>(fDateFormat)) == NULL) {
-            status = U_UNSUPPORTED_ERROR;
-            return;
-        }
-        if (fTimeFormat && (stmfmt = dynamic_cast<SimpleDateFormat*>(fTimeFormat)) == NULL) {
-            status = U_UNSUPPORTED_ERROR;
-            return;
-        }
-        if ( fDateFormat ) {
-            sdtfmt->applyPattern(datePattern);
-        }
-        if ( fTimeFormat ) {
-            stmfmt->applyPattern(timePattern);
-        }
+        fDatePattern.setTo(datePattern);
+        fTimePattern.setTo(timePattern);
     }
 }
 
 const DateFormatSymbols*
 RelativeDateFormat::getDateFormatSymbols() const
 {
-    SimpleDateFormat* sdtfmt = NULL;
-    if (fDateFormat && (sdtfmt = dynamic_cast<SimpleDateFormat*>(fDateFormat)) != NULL) {
-        return sdtfmt->getDateFormatSymbols();
-    }
-    return NULL;
+    return fDateTimeFormatter->getDateFormatSymbols();
 }
 
 void RelativeDateFormat::loadDates(UErrorCode &status) {
index d7eed6e4582d2fe97a3ae69eda33b9ebc0160e26..1206ea3add0c04d008c55ed5d5c493e6b37a9fec 100644 (file)
@@ -1,6 +1,6 @@
 /*
 *******************************************************************************
-* Copyright (C) 2007-2011, International Business Machines Corporation and    *
+* Copyright (C) 2007-2012, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
@@ -18,6 +18,7 @@
 #if !UCONFIG_NO_FORMATTING
 
 #include "unicode/datefmt.h"
+#include "unicode/smpdtfmt.h"
 
 U_NAMESPACE_BEGIN
 
@@ -233,12 +234,12 @@ public:
 
 
 private:
-    DateFormat *fDateFormat; // the held date format
-    DateFormat *fTimeFormat; // the held time format
+    SimpleDateFormat *fDateTimeFormatter;
+    UnicodeString fDatePattern;
+    UnicodeString fTimePattern;
     MessageFormat *fCombinedFormat; //  the {0} {1} format.
 
     UDateFormatStyle fDateStyle;
-    UDateFormatStyle fTimeStyle;
     Locale  fLocale;
 
     int32_t fDayMin;    // day id of lowest #
index cad794ab3092de863693026856086ed1e4c40af6..0d2ab2521d3f189177c3adbaee405a352ed24c62 100644 (file)
@@ -396,6 +396,9 @@ static const UChar newTimePatn[] = { 0x0048, 0x0048, 0x002C, 0x006D, 0x006D, 0 }
 static const UChar minutesStr[] = { 0x0034, 0x0039, 0 }; /* "49", minutes string to search for in output */
 enum { kDateOrTimeOutMax = 96, kDateAndTimeOutMax = 192 };
 
+static const UDate minutesTolerance = 2 * 60.0 * 1000.0;
+static const UDate daysTolerance = 2 * 24.0 * 60.0 * 60.0 * 1000.0;
+
 static void TestRelativeDateFormat()
 {
     UDate today = 0.0;
@@ -423,10 +426,10 @@ static void TestRelativeDateFormat()
         UDateFormat* fmtTime;
         int32_t dayOffset, limit;
         UFieldPosition fp;
-               UChar   strDateTime[kDateAndTimeOutMax];
-               UChar   strDate[kDateOrTimeOutMax];
-               UChar   strTime[kDateOrTimeOutMax];
-               UChar * strPtr;
+        UChar   strDateTime[kDateAndTimeOutMax];
+        UChar   strDate[kDateOrTimeOutMax];
+        UChar   strTime[kDateOrTimeOutMax];
+        UChar * strPtr;
         int32_t dtpatLen;
 
         fmtRelDateTime = udat_open(UDAT_SHORT, *stylePtr | UDAT_RELATIVE, trdfLocale, trdfZone, -1, NULL, 0, &status);
@@ -450,37 +453,37 @@ static void TestRelativeDateFormat()
 
         dtpatLen = udat_toPatternRelativeDate(fmtRelDateTime, strDate, kDateAndTimeOutMax, &status);
         if ( U_FAILURE(status) ) {
-               log_err("udat_toPatternRelativeDate timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
-               status = U_ZERO_ERROR;
+            log_err("udat_toPatternRelativeDate timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
+            status = U_ZERO_ERROR;
         } else if ( u_strstr(strDate, *monthPtnPtr) == NULL || dtpatLen != u_strlen(strDate) ) {
-               log_err("udat_toPatternRelativeDate timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) date pattern incorrect\n", *stylePtr );
+            log_err("udat_toPatternRelativeDate timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) date pattern incorrect\n", *stylePtr );
         }
         dtpatLen = udat_toPatternRelativeTime(fmtRelDateTime, strTime, kDateAndTimeOutMax, &status);
         if ( U_FAILURE(status) ) {
-               log_err("udat_toPatternRelativeTime timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
-               status = U_ZERO_ERROR;
+            log_err("udat_toPatternRelativeTime timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
+            status = U_ZERO_ERROR;
         } else if ( u_strstr(strTime, minutesPatn) == NULL || dtpatLen != u_strlen(strTime) ) {
-               log_err("udat_toPatternRelativeTime timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) time pattern incorrect\n", *stylePtr );
+            log_err("udat_toPatternRelativeTime timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) time pattern incorrect\n", *stylePtr );
         }
         dtpatLen = udat_toPattern(fmtRelDateTime, FALSE, strDateTime, kDateAndTimeOutMax, &status);
         if ( U_FAILURE(status) ) {
-               log_err("udat_toPattern timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
-               status = U_ZERO_ERROR;
+            log_err("udat_toPattern timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
+            status = U_ZERO_ERROR;
         } else if ( u_strstr(strDateTime, strDate) == NULL || u_strstr(strDateTime, strTime) == NULL || dtpatLen != u_strlen(strDateTime) ) {
-               log_err("udat_toPattern timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) dateTime pattern incorrect\n", *stylePtr );
+            log_err("udat_toPattern timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) dateTime pattern incorrect\n", *stylePtr );
         }
         udat_applyPatternRelative(fmtRelDateTime, strDate, u_strlen(strDate), newTimePatn, u_strlen(newTimePatn), &status);
         if ( U_FAILURE(status) ) {
-               log_err("udat_applyPatternRelative timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
-               status = U_ZERO_ERROR;
+            log_err("udat_applyPatternRelative timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
+            status = U_ZERO_ERROR;
         } else {
-               udat_toPattern(fmtRelDateTime, FALSE, strDateTime, kDateAndTimeOutMax, &status);
-               if ( U_FAILURE(status) ) {
-                       log_err("udat_toPattern timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
-                       status = U_ZERO_ERROR;
-               } else if ( u_strstr(strDateTime, newTimePatn) == NULL ) {
-                       log_err("udat_applyPatternRelative timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) didn't update time pattern\n", *stylePtr );
-               }
+            udat_toPattern(fmtRelDateTime, FALSE, strDateTime, kDateAndTimeOutMax, &status);
+            if ( U_FAILURE(status) ) {
+                log_err("udat_toPattern timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
+                status = U_ZERO_ERROR;
+            } else if ( u_strstr(strDateTime, newTimePatn) == NULL ) {
+                log_err("udat_applyPatternRelative timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) didn't update time pattern\n", *stylePtr );
+            }
         }
         udat_applyPatternRelative(fmtRelDateTime, strDate, u_strlen(strDate), strTime, u_strlen(strTime), &status); /* restore original */
 
@@ -493,12 +496,30 @@ static void TestRelativeDateFormat()
                 log_err("udat_format timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
                 status = U_ZERO_ERROR;
             } else {
+                int32_t parsePos = 0;
+                UDate dateResult = udat_parse(fmtRelDateTime, strDateTime, -1, &parsePos, &status);
+                UDate dateDiff =  (dateResult >= dateToUse)? dateResult - dateToUse: dateToUse - dateResult;
+                if ( U_FAILURE(status) || dateDiff > minutesTolerance ) {
+                    log_err("udat_parse timeStyle SHORT dateStyle (%d | UDAT_RELATIVE) fails, error %s, expect approx %.1f, got %.1f, parsePos %d\n",
+                            *stylePtr, myErrorName(status), dateToUse, dateResult, parsePos );
+                    status = U_ZERO_ERROR;
+                } 
+
                 udat_format(fmtRelDate, dateToUse, strDate, kDateOrTimeOutMax, NULL, &status);
                 if ( U_FAILURE(status) ) {
                     log_err("udat_format timeStyle NONE dateStyle (%d | UDAT_RELATIVE) fails, error %s\n", *stylePtr, myErrorName(status) );
                     status = U_ZERO_ERROR;
                 } else if ( u_strstr(strDateTime, strDate) == NULL ) {
                     log_err("relative date string not found in udat_format timeStyle SHORT dateStyle (%d | UDAT_RELATIVE)\n", *stylePtr );
+                } else {
+                    parsePos = 0;
+                    dateResult = udat_parse(fmtRelDate, strDate, -1, &parsePos, &status);
+                    dateDiff =  (dateResult >= dateToUse)? dateResult - dateToUse: dateToUse - dateResult;
+                    if ( U_FAILURE(status) || dateDiff > daysTolerance ) {
+                        log_err("udat_parse timeStyle NONE dateStyle (%d | UDAT_RELATIVE) fails, error %s, expect approx %.1f, got %.1f, parsePos %d\n",
+                                *stylePtr, myErrorName(status), dateToUse, dateResult, parsePos );
+                        status = U_ZERO_ERROR;
+                    } 
                 }
 
                 udat_format(fmtTime, dateToUse, strTime, kDateOrTimeOutMax, NULL, &status);
@@ -1194,7 +1215,7 @@ static void TestRelativeCrash(void) {
             }            
         }
         {
-               UChar erabuf[32];
+            UChar erabuf[32];
             UErrorCode subStatus = U_ZERO_ERROR;
             what = "udat_getSymbols";
             log_verbose("Trying %s on a relative date..\n", what);
index a3c58362f159c944c34bc8091793163a948716b6..c3a533aede8b7ee21ba6ba8c076882187e2cf27b 100644 (file)
@@ -1,6 +1,6 @@
 /********************************************************************
  * COPYRIGHT: 
- * Copyright (c) 1997-2010, International Business Machines Corporation and
+ * Copyright (c) 1997-2010,2012, International Business Machines Corporation and
  * others. All Rights Reserved.
  ********************************************************************/
 
@@ -73,14 +73,16 @@ void DataDrivenFormatTest::runIndexedTest(int32_t index, UBool exec,
 
 
 /*
- *             Headers { "locale","spec", "date", "str"}
+ *             Headers { "locale", "zone", "spec", "date", "str"}
             // locale: locale including calendar type
+            // zone:   time zone name, or "" to not explicitly set zone
             // spec:   either 'PATTERN=y mm h' etc, or 'DATE=SHORT,TIME=LONG'
             // date:   either an unsigned long (millis), or a calendar spec ERA=0,YEAR=1, etc.. applied to the calendar type specified by the locale
             // str:   the expected unicode string
             Cases { 
                {
-                    "en_US@calendar=gregorian",         
+                    "en_US@calendar=gregorian",
+                    "",
                     "DATE=SHORT,TIME=SHORT",
                     "ERA=1,YEAR=2007,MONTH=AUGUST,DATE=8,HOUR=18,MINUTE=54,SECOND=12",
                     "8/8/2007 6:54pm"
@@ -130,6 +132,11 @@ void DataDrivenFormatTest::testConvertDate(TestData *testData,
             errln("case %d: No 'locale' line.", n);
             continue;
         }
+        UnicodeString zone = currentCase->getString("zone", status);
+        if (U_FAILURE(status)) {
+            errln("case %d: No 'zone' line.", n);
+            continue;
+        }
         UnicodeString spec = currentCase->getString("spec", status);
         if(U_FAILURE(status)) {
             errln("case %d: No 'spec' line.", n);
@@ -175,6 +182,13 @@ void DataDrivenFormatTest::testConvertDate(TestData *testData,
         if(U_FAILURE(status)) {
             errln("case %d: could not create calendar from %s", n, calLoc);
         }
+        
+        if (zone.length() > 0) {
+            TimeZone * tz = TimeZone::createTimeZone(zone);
+            cal->setTimeZone(*tz);
+            format->setTimeZone(*tz);
+            delete tz;
+        }
 
         // parse 'date'
         if(date.startsWith(kMILLIS)) {
@@ -197,8 +211,13 @@ void DataDrivenFormatTest::testConvertDate(TestData *testData,
             for (int q=0; q<UCAL_FIELD_COUNT; q++) {
                 if (fromSet.isSet((UCalendarDateFields)q)) {
                     //int32_t oldv = cal->get((UCalendarDateFields)q, status);
-                    cal->add((UCalendarDateFields)q,
-                                fromSet.get((UCalendarDateFields)q), status);
+                    if (q == UCAL_DATE) {
+                        cal->add((UCalendarDateFields)q,
+                                    fromSet.get((UCalendarDateFields)q), status);
+                    } else {
+                        cal->set((UCalendarDateFields)q,
+                                    fromSet.get((UCalendarDateFields)q));
+                    }
                     //int32_t newv = cal->get((UCalendarDateFields)q, status);
                 }
             }
index a941b23ce848f03aeed2d4adc8d698057571da0c..9827bd6fce9f27a25c3fa4812cf869f9f0de609f 100644 (file)
@@ -19,23 +19,27 @@ format:table(nofallback) {
                     Type { "date_parse" }
                 },
             }
-            Headers { "locale","spec", "date", "str"}
+            Headers { "locale", "zone", "spec", "date", "str"}
             // locale: locale including calendar type
+            // zone:   time zone name, or "" to not explicitly set zone
             // spec:   either 'PATTERN=y mm h' etc, or 'DATE=SHORT,TIME=LONG'
             // date:   either 'MILLIS=####' where #### is millis,
             //        or a calendar spec ERA=0,YEAR=1, etc.. applied to the calendar type specified by the locale
             //        or RELATIVE_MILLIS=### where ### is a signed value which is added to the current millis
-            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time
+            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time,
+            //           and any other fields present will be set explicitly.
             // str:   the expected unicode string
             Cases { 
                {
-                    "en_US@calendar=gregorian",         
+                    "en_US@calendar=gregorian",
+                    "",
                     "DATE=SHORT,TIME=SHORT",
                     "ERA=1,YEAR=2007,MONTH=AUGUST,DATE=8,HOUR_OF_DAY=18,MINUTE=54,SECOND=0",
                     "8/8/07, 6:54 PM"
                },
                {
                     "zh_TW@calendar=roc",
+                    "",
                     "DATE=LONG",
                     "ERA=1,YEAR=98,MONTH=0,DATE=24",
                     "民國98年1月24日",
@@ -43,6 +47,7 @@ format:table(nofallback) {
                {
                     //民國前2年1月24日 -> 1910-1-24 
                     "zh_TW@calendar=roc",
+                    "",
                     "DATE=LONG",
                     "ERA=0,YEAR=2,MONTH=0,DATE=24",
                     "民國前2年1月24日",
@@ -58,46 +63,147 @@ format:table(nofallback) {
                     Type { "date_format" }
                 },
             }
-            Headers { "locale","spec", "date", "str"}
+            Headers { "locale", "zone", "spec", "date", "str"}
             Cases { 
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "DATE=RELATIVE_SHORT",
                     "RELATIVE_ADD:DATE=1", // one day from now
                     "Tomorrow"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "DATE=RELATIVE_SHORT",
                     "RELATIVE_MILLIS=0", // today
                     "Today"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "DATE=RELATIVE_SHORT",
                     "RELATIVE_ADD:DATE=-1", // one day before now
                     "Yesterday"
                },
+               // date only, out of relative range
+               {
+                    "en_US@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_FULL",
+                    "ERA=1,YEAR=2012,MONTH=OCTOBER,DATE=8,HOUR_OF_DAY=23,MINUTE=59,SECOND=0",
+                    "Monday, October 8, 2012"
+               },
+               // time only
+               {
+                    "en_US@calendar=gregorian",         
+                    "GMT",
+                    "TIME=LONG",
+                    "ERA=1,YEAR=2012,MONTH=OCTOBER,DATE=8,HOUR_OF_DAY=23,MINUTE=59,SECOND=0",
+                    "11:59:00 PM GMT"
+               },
+               {
+                    "en_US@calendar=gregorian",         
+                    "GMT",
+                    "TIME=LONG",
+                    "RELATIVE_ADD:DATE=-1,HOUR_OF_DAY=17,MINUTE=0,SECOND=0", // one day before now at specified time
+                    "5:00:00 PM GMT"
+               },
+               // normal formats, combined using 'at'
+               {
+                    "en_US@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_FULL,TIME=LONG",
+                    "ERA=1,YEAR=2012,MONTH=OCTOBER,DATE=8,HOUR_OF_DAY=23,MINUTE=59,SECOND=0",
+                    "Monday, October 8, 2012 at 11:59:00 PM GMT"
+               },
+               // normal formats, combined using ", "
+               {
+                    "en_US@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_MEDIUM,TIME=SHORT",
+                    "ERA=1,YEAR=2012,MONTH=OCTOBER,DATE=8,HOUR_OF_DAY=23,MINUTE=59,SECOND=0",
+                    "Oct 8, 2012, 11:59 PM"
+               },
+               // formats with relative day, combined using 'at'
+               {
+                    "en_US@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_FULL,TIME=LONG",
+                    "RELATIVE_ADD:DATE=-1,HOUR_OF_DAY=17,MINUTE=0,SECOND=0", // one day before now at specified time
+                    "Yesterday at 5:00:00 PM GMT"
+               },
+               // formats with relative day, combined using ", "
+               {
+                    "en_US@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_MEDIUM,TIME=SHORT",
+                    "RELATIVE_ADD:DATE=-1,HOUR_OF_DAY=17,MINUTE=0,SECOND=0", // one day before now at specified time
+                    "Yesterday, 5:00 PM"
+               },
+               // normal formats that have quoted literals, combined
+               {
+                    "pt@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_FULL,TIME=LONG",
+                    "ERA=1,YEAR=2012,MONTH=OCTOBER,DATE=8,HOUR_OF_DAY=23,MINUTE=59,SECOND=0",
+                    "segunda-feira, 8 de outubro de 2012 23:59:00 Horário de Greenwich"
+               },
+               // vi combined formats have time first
+               {
+                    "vi@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_LONG,TIME=MEDIUM",
+                    "ERA=1,YEAR=2012,MONTH=OCTOBER,DATE=8,HOUR_OF_DAY=23,MINUTE=59,SECOND=0",
+                    "23:59:00 Ngày 08 tháng 10 năm 2012"
+               },
+               {
+                    "vi@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_LONG,TIME=MEDIUM",
+                    "RELATIVE_ADD:DATE=-1,HOUR_OF_DAY=17,MINUTE=0,SECOND=0", // one day before now at specified time
+                    "17:00:00 Ngày hôm qua"
+               },
+               // el combines formats using hyphen
+               {
+                    "el@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_LONG,TIME=MEDIUM",
+                    "ERA=1,YEAR=2012,MONTH=OCTOBER,DATE=8,HOUR_OF_DAY=23,MINUTE=59,SECOND=0",
+                    "8 Οκτωβρίου 2012 - 11:59:00 μ.μ."
+               },
+               {
+                    "el@calendar=gregorian",         
+                    "GMT",
+                    "DATE=RELATIVE_LONG,TIME=MEDIUM",
+                    "RELATIVE_ADD:DATE=-1,HOUR_OF_DAY=17,MINUTE=0,SECOND=0", // one day before now at specified time
+                    "Χτες - 5:00:00 μ.μ."
+               },
+              // other tests
                {
                     "mt_MT@calendar=gregorian",         
+                    "",
                     "DATE=RELATIVE_SHORT",
                     "RELATIVE_ADD:DATE=1", // one day from now
                     "Għada"
                },
                {
                     "mt_MT@calendar=gregorian",         
+                    "",
                     "DATE=RELATIVE_SHORT",
                     "RELATIVE_MILLIS=0", // today
                     "Illum"
                },
                {
                     "mt_MT@calendar=gregorian",         
+                    "",
                     "DATE=RELATIVE_SHORT",
                     "RELATIVE_ADD:DATE=-1", // one day before now
                     "Lbieraħ"
                },
                {
                     "ru",
+                    "",
                     "DATE=RELATIVE_SHORT",
                     "RELATIVE_ADD:DATE=-2", // 2 days ago
                     "Позавчера"
@@ -118,13 +224,15 @@ format:table(nofallback) {
 //                    Type { "date_parse" }
 //                },
             }
-            Headers { "locale","spec", "date", "str"}
+            Headers { "locale", "zone", "spec", "date", "str"}
             // locale: locale including calendar type
+            // zone:   time zone name, or "" to not explicitly set zone
             // spec:   either 'PATTERN=y mm h' etc, or 'DATE=SHORT,TIME=LONG'
             // date:   either 'MILLIS=####' where #### is millis,
             //        or a calendar spec ERA=0,YEAR=1, etc.. applied to the calendar type specified by the locale
             //        or RELATIVE_MILLIS=### where ### is a signed value which is added to the current millis
-            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time
+            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time,
+            //           and any other fields present will be set explicitly.
             // str:   the expected unicode string
            
 // from CLDR UTS 35: 
@@ -134,30 +242,35 @@ format:table(nofallback) {
 //AD 1             1   01      001     0001    00001
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G y",
                     "YEAR=1",
                     "AD 1"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yy",
                     "YEAR=1",
                     "AD 01"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyy",
                     "YEAR=1",
                     "AD 001"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyy",
                     "YEAR=1",
                     "AD 0001"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyyy",
                     "YEAR=1",
                     "AD 00001"
@@ -165,30 +278,35 @@ format:table(nofallback) {
 //AD 12        12      12      012     0012    00012
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G y",
                     "YEAR=12",
                     "AD 12"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yy",
                     "YEAR=12",
                     "AD 12"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyy",
                     "YEAR=12",
                     "AD 012"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyy",
                     "YEAR=12",
                     "AD 0012"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyyy",
                     "YEAR=12",
                     "AD 00012"
@@ -196,30 +314,35 @@ format:table(nofallback) {
 //AD 123       123     23      123     0123    00123
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G y",
                     "YEAR=123",
                     "AD 123"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yy",
                     "YEAR=123",
                     "AD 23"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyy",
                     "YEAR=123",
                     "AD 123"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyy",
                     "YEAR=123",
                     "AD 0123"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyyy",
                     "YEAR=123",
                     "AD 00123"
@@ -227,30 +350,35 @@ format:table(nofallback) {
 //AD 1234      1234    34      1234    1234    01234
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G y",
                     "YEAR=1234",
                     "AD 1234"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yy",
                     "YEAR=1234",
                     "AD 34"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyy",
                     "YEAR=1234",
                     "AD 1234"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyy",
                     "YEAR=1234",
                     "AD 1234"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyyy",
                     "YEAR=1234",
                     "AD 01234"
@@ -258,30 +386,35 @@ format:table(nofallback) {
 //AD 12345     12345   45      12345   12345   12345
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G y",
                     "YEAR=12345",
                     "AD 12345"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yy",
                     "YEAR=12345",
                     "AD 45"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyy",
                     "YEAR=12345",
                     "AD 12345"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyy",
                     "YEAR=12345",
                     "AD 12345"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyyy",
                     "YEAR=12345",
                     "AD 12345"
@@ -301,13 +434,15 @@ format:table(nofallback) {
                     Type { "date_parse" }
                 },
             }
-            Headers { "locale","spec", "date", "str"}
+            Headers { "locale", "zone", "spec", "date", "str"}
             // locale: locale including calendar type
+            // zone:   time zone name, or "" to not explicitly set zone
             // spec:   either 'PATTERN=y mm h' etc, or 'DATE=SHORT,TIME=LONG'
             // date:   either 'MILLIS=####' where #### is millis,
             //        or a calendar spec ERA=0,YEAR=1, etc.. applied to the calendar type specified by the locale
             //        or RELATIVE_MILLIS=### where ### is a signed value which is added to the current millis
-            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time
+            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time,
+            //           and any other fields present will be set explicitly.
             // str:   the expected unicode string
            
 // from CLDR UTS 35: 
@@ -317,30 +452,35 @@ format:table(nofallback) {
 //AD 1             1   01      001     0001    00001
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G y",
                     "YEAR=2008",
                     "AD 2008"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yy",
                     "YEAR=2008",
                     "AD 08"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyy",
                     "YEAR=2008",
                     "AD 2008"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyy",
                     "YEAR=2008",
                     "AD 2008"
                },
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=G yyyyy",
                     "YEAR=2008",
                     "AD 02008"
@@ -349,30 +489,35 @@ format:table(nofallback) {
                                // Japanese
                {
                     "en_US@calendar=japanese",         
+                    "",
                     "PATTERN=G y",
                     "YEAR=8",
                     "Heisei 8"
                },
                {
                     "en_US@calendar=japanese",         
+                    "",
                     "PATTERN=G yy",
                     "YEAR=8",
                     "Heisei 08"
                },
                {
                     "en_US@calendar=japanese",         
+                    "",
                     "PATTERN=G yyy",
                     "YEAR=8",
                     "Heisei 008"
                },
                {
                     "en_US@calendar=japanese",         
+                    "",
                     "PATTERN=G yyyy",
                     "YEAR=8",
                     "Heisei 0008"
                },
                {
                     "en_US@calendar=japanese",         
+                    "",
                     "PATTERN=G yyyyy",
                     "YEAR=8",
                     "Heisei 00008"
@@ -390,18 +535,21 @@ format:table(nofallback) {
                     Type { "date_format" }
                 },
             }
-            Headers { "locale","spec", "date", "str"}
+            Headers { "locale", "zone", "spec", "date", "str"}
             // locale: locale including calendar type
+            // zone:   time zone name, or "" to not explicitly set zone
             // spec:   either 'PATTERN=y mm h' etc, or 'DATE=SHORT,TIME=LONG'
             // date:   either 'MILLIS=####' where #### is millis,
             //        or a calendar spec ERA=0,YEAR=1, etc.. applied to the calendar type specified by the locale
             //        or RELATIVE_MILLIS=### where ### is a signed value which is added to the current millis
-            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time
+            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time,
+            //           and any other fields present will be set explicitly.
             // str:   the expected unicode string
             
             Cases { 
                {
                     "en_US@calendar=hebrew",         
+                    "",
                     "DATE=FULL,TIME=FULL",
                     "MILLIS=3076424179200000",
                     "Friday, Heshvan 3, 103217 at 12:00:00 AM GMT-08:00"
@@ -419,18 +567,21 @@ format:table(nofallback) {
                     Type { "date_parse" }
                 },
             }
-            Headers { "locale","spec", "date", "str"}
+            Headers { "locale", "zone", "spec", "date", "str"}
             // locale: locale including calendar type
+            // zone:   time zone name, or "" to not explicitly set zone
             // spec:   either 'PATTERN=y mm h' etc, or 'DATE=SHORT,TIME=LONG'
             // date:   either 'MILLIS=####' where #### is millis,
             //        or a calendar spec ERA=0,YEAR=1, etc.. applied to the calendar type specified by the locale
             //        or RELATIVE_MILLIS=### where ### is a signed value which is added to the current millis
-            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time
+            //        or RELATIVE_ADD:DATE=1  which means that the field "DATE" will be added by +1 relative to current time,
+            //           and any other fields present will be set explicitly.
             // str:   the expected unicode string
             
             Cases { 
                {
                     "en_US@calendar=gregorian",         
+                    "",
                     "PATTERN=YYYYHHmmssEEEww",
                     "YEAR=1999,HOUR_OF_DAY=4,MINUTE=5,SECOND=6,DAY_OF_WEEK=2,WEEK_OF_YEAR=4",
                // won't roundtrip.