1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2007-2015, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.dev.test.duration; 10 11 import java.math.BigDecimal; 12 import java.text.FieldPosition; 13 import java.util.Date; 14 import java.util.HashMap; 15 import java.util.Map; 16 import java.util.MissingResourceException; 17 18 import javax.xml.datatype.DatatypeConstants; 19 import javax.xml.datatype.DatatypeConstants.Field; 20 import javax.xml.datatype.Duration; 21 22 import org.junit.Test; 23 import org.junit.runner.RunWith; 24 import org.junit.runners.JUnit4; 25 26 import com.ibm.icu.dev.test.TestFmwk; 27 import com.ibm.icu.text.DurationFormat; 28 import com.ibm.icu.util.Calendar; 29 import com.ibm.icu.util.ULocale; 30 31 /** 32 * @author srl 33 * 34 */ 35 @RunWith(JUnit4.class) 36 public class ICUDurationTest extends TestFmwk { 37 /** 38 * Allows us to not depend on javax.xml.datatype.DatatypeFactory. 39 * We need just a tiny subset of the Duration API: 40 * The ICU DurationFormat just extracts the field values, 41 * to convert the Duration into an internal Period type. 42 */ 43 private static final class ICUTestDuration extends javax.xml.datatype.Duration { 44 private final int sign; 45 // Duration docs say BigInteger/BigDecimal but 46 // ICU only cares about intValue() and floatValue(). 47 private final Map<Field, Number> fields; 48 49 ICUTestDuration(long millis) { 50 fields = new HashMap<Field, Number>(); 51 if (millis > 0) { 52 sign = 1; 53 } else if (millis == 0) { 54 sign = 0; 55 return; 56 } else { 57 sign = -1; 58 millis = -millis; 59 } 60 long d = millis / 86400000L; 61 millis %= 86400000L; 62 if (d > 0) { 63 fields.put(DatatypeConstants.DAYS, d); 64 } 65 long h = millis / 3600000L; 66 millis %= 3600000L; 67 if (h > 0) { 68 fields.put(DatatypeConstants.HOURS, h); 69 } 70 long m = millis / 60000L; 71 millis %= 60000L; 72 if (m > 0) { 73 fields.put(DatatypeConstants.MINUTES, m); 74 } 75 fields.put(DatatypeConstants.SECONDS, (float)millis / 1000); 76 } 77 78 /** 79 * Pass in negative values for fields not to be set. 80 */ 81 ICUTestDuration(int sgn, int y, int months, int d, int h, int m, float s) { 82 sign = sgn; 83 fields = new HashMap<Field, Number>(); 84 if (y >= 0) { fields.put(DatatypeConstants.YEARS, y); } 85 if (months >= 0) { fields.put(DatatypeConstants.MONTHS, months); } 86 if (d >= 0) { fields.put(DatatypeConstants.DAYS, d); } 87 if (h >= 0) { fields.put(DatatypeConstants.HOURS, h); } 88 if (m >= 0) { fields.put(DatatypeConstants.MINUTES, m); } 89 if (s >= 0) { fields.put(DatatypeConstants.SECONDS, s); } 90 } 91 92 private ICUTestDuration(int sgn, Map<Field, Number> f) { 93 sign = sgn; 94 fields = f; 95 } 96 97 @Override 98 public Duration add(Duration rhs) { 99 throw new UnsupportedOperationException(); 100 } 101 102 @Override 103 public void addTo(java.util.Calendar calendar) { 104 throw new UnsupportedOperationException(); 105 } 106 107 @Override 108 public int compare(Duration duration) { 109 throw new UnsupportedOperationException(); 110 } 111 112 @Override 113 public Number getField(Field field) { 114 return fields.get(field); 115 } 116 117 @Override 118 public int getSign() { 119 return sign; 120 } 121 122 @Override 123 public int hashCode() { 124 throw new UnsupportedOperationException(); 125 } 126 127 @Override 128 public boolean isSet(Field field) { 129 return fields.containsKey(field); 130 } 131 132 @Override 133 public Duration multiply(BigDecimal factor) { 134 throw new UnsupportedOperationException(); 135 } 136 137 @Override 138 public Duration negate() { 139 return new ICUTestDuration(-sign, fields); 140 } 141 142 @Override 143 public Duration normalizeWith(java.util.Calendar startTimeInstant) { 144 throw new UnsupportedOperationException(); 145 } 146 147 @Override 148 public String toString() { 149 String signString = sign > 0 ? "positive" : sign == 0 ? "zero" : "negative"; 150 return signString + " fields=" + fields; 151 } 152 } 153 private static final ICUTestDuration newDuration(long millis) { 154 return new ICUTestDuration(millis); 155 } 156 private static final ICUTestDuration newDuration(int sgn, int d, int h, int m, float s) { 157 return new ICUTestDuration(sgn, -1, -1, d, h, m, s); 158 } 159 private static final ICUTestDuration newDuration(int sgn, int h, int m, float s) { 160 return new ICUTestDuration(sgn, -1, -1, -1, h, m, s); 161 } 162 private static final ICUTestDuration newDuration(int sgn, float s) { 163 return new ICUTestDuration(sgn, -1, -1, -1, -1, -1, s); 164 } 165 166 /** 167 * 168 */ 169 public ICUDurationTest() { 170 } 171 172 /** 173 * Basic test 174 */ 175 @Test 176 public void TestBasics() { 177 DurationFormat df; 178 String expect; 179 String formatted; 180 181 df = DurationFormat.getInstance(new ULocale("it")); 182 formatted = df.formatDurationFromNow(4096); 183 expect = "fra quattro secondi"; 184 if(!expect.equals(formatted)) { 185 errln("Expected " + expect + " but got " + formatted); 186 } else { 187 logln("format duration -> " + formatted); 188 } 189 190 formatted = df.formatDurationFromNowTo(new Date(0)); 191 Calendar cal = Calendar.getInstance(); 192 int years = cal.get(Calendar.YEAR) - 1970; // year of Date(0) 193 expect = years + " anni fa"; 194 if(!expect.equals(formatted)) { 195 errln("Expected " + expect + " but got " + formatted); 196 } else { 197 logln("format date -> " + formatted); 198 } 199 200 formatted = df.formatDurationFrom(1000*3600*24, new Date(0).getTime()); 201 expect = "fra un giorno"; 202 if(!expect.equals(formatted)) { 203 errln("Expected " + expect + " but got " + formatted); 204 } else { 205 logln("format date from -> " + formatted); 206 } 207 208 formatted = df.format(new Long(1000*3600*24*2)); 209 expect = "fra due giorni"; 210 if(!expect.equals(formatted)) { 211 errln("Expected " + expect + " but got " + formatted); 212 } else { 213 logln("format long obj -> " + formatted); 214 } 215 } 216 217 @Test 218 public void TestSimpleXMLDuration() { 219 Duration d; 220 DurationFormat df; 221 String out; 222 String expected; 223 String expected2; 224 225 // test 1 226 d = newDuration(1, 2, 46, 40); // "PT2H46M40S" 227 df = DurationFormat.getInstance(new ULocale("en")); 228 expected = "2 hours, 46 minutes, and 40 seconds"; 229 out = df.format(d); 230 if(out.equals(expected)) { 231 logln("out=expected: " + expected + " from " + d); 232 } else { 233 errln("FAIL: got " + out + " wanted " + expected + " from " + d); 234 } 235 236 // test 2 237 d = newDuration(10000); 238 df = DurationFormat.getInstance(new ULocale("en")); 239 expected = "10 seconds"; 240 out = df.format(d); 241 if(out.equals(expected)) { 242 logln("out=expected: " + expected + " from " + d); 243 } else { 244 errln("FAIL: got " + out + " wanted " + expected + " from " + d); 245 } 246 // test 3 247 d = newDuration(1, 0, 0, 0, 10); // "P0DT0H0M10.0S" 248 df = DurationFormat.getInstance(new ULocale("en")); 249 expected = "10 seconds"; 250 out = df.format(d); 251 if(out.equals(expected)) { 252 logln("out=expected: " + expected + " from " + d); 253 } else { 254 errln("FAIL: got " + out + " wanted " + expected + " from " + d); 255 } 256 // test 4 257 d = newDuration(86400000); 258 df = DurationFormat.getInstance(new ULocale("en")); 259 expected = "1 day, 0 hours, 0 minutes, and 0 seconds"; 260 expected2 = "1 day and 0 seconds"; // This is the expected result for Windows with IBM JRE6 261 out = df.format(d); 262 if(out.equals(expected)) { 263 logln("out=expected: " + expected + " from " + d); 264 } else { 265 if(out.equals(expected2)){ 266 logln("WARNING: got " + out + " wanted " + expected + " from " + d); 267 } else{ 268 errln("FAIL: got " + out + " wanted " + expected + " from " + d); 269 } 270 } 271 } 272 273 @Test 274 public void TestXMLDuration() { 275 final class TestCase { 276 final String localeString; 277 final ULocale locale; 278 final String durationString; 279 final Duration duration; 280 final String expected; 281 282 TestCase(String loc, String ds, Duration d, String exp) { 283 localeString = loc; 284 locale = new ULocale(loc); 285 durationString = ds; 286 duration = d; 287 expected = exp; 288 } 289 } 290 291 TestCase cases[] = { 292 new TestCase("en", "PT10.00099S", newDuration(1, 10.00099F), "10 seconds"), 293 new TestCase("en", "#10000", newDuration(10000), "10 seconds"), 294 new TestCase("en", "-PT10.00099S", newDuration(-1, 10.00099F), "10 seconds"), 295 new TestCase("en", "#-10000", newDuration(-10000), "10 seconds"), 296 297 // from BD req's 298 new TestCase("en", "PT2H46M40S", newDuration(1, 2, 46, 40), "2 hours, 46 minutes, and 40 seconds"), 299 new TestCase("it", "PT2H46M40S", newDuration(1, 2, 46, 40), "due ore, 46 minuti e 40 secondi"), 300 301 // more cases 302 new TestCase("en", "PT10S", newDuration(1, 10), "10 seconds"), 303 new TestCase("en", "PT88M70S", newDuration(1, -1, 88, 70), "88 minutes and 70 seconds"), 304 new TestCase("en", "PT10.100S", newDuration(1, 10.100F), "10 seconds and 100 milliseconds"), 305 new TestCase("en", "-PT10S", newDuration(-1, 10), "10 seconds"), 306 new TestCase("en", "PT0H5M0S", newDuration(1, 0, 5, 0), "5 minutes and 0 seconds") 307 }; 308 309 for (TestCase tc : cases) { 310 String loc = tc.localeString; 311 String from = tc.durationString; 312 String to = tc.expected; 313 314 ULocale locale = tc.locale; 315 Duration d = tc.duration; 316 317 DurationFormat df = DurationFormat.getInstance(locale); 318 String output = df.format(d); 319 320 if(output.equals(to)) { 321 logln("SUCCESS: locale: " + loc + ", from " + from + " ["+d.toString()+"] " +" to " + to + "= " + output); 322 } else { 323 logln("FAIL: locale: " + loc + ", from " + from + " ["+d.toString()+"] " +": expected " + to + " got " + output); 324 } 325 } 326 } 327 328 329 @Test 330 public void TestBadObjectError() { 331 Runtime r = Runtime.getRuntime(); 332 DurationFormat df = DurationFormat.getInstance(new ULocale("en")); 333 String output = null; 334 try { 335 output = df.format(r); 336 errln("FAIL: did NOT get IllegalArgumentException! Should have. Formatted Runtime as " + output + " ???"); 337 } catch (IllegalArgumentException iae) { 338 logln("PASS: expected: Caught iae: " + iae.toString() ); 339 } 340 // try a second time, because it is a different code path for java < 1.5 341 try { 342 output = df.format(r); 343 errln("FAIL: [#2] did NOT get IllegalArgumentException! Should have. Formatted Runtime as " + output + " ???"); 344 } catch (IllegalArgumentException iae) { 345 logln("PASS: [#2] expected: Caught iae: " + iae.toString() ); 346 } 347 } 348 349 @Test 350 public void TestBadLocaleError() { 351 try { 352 DurationFormat df = DurationFormat.getInstance(new ULocale("und")); 353 df.format(new Date()); 354 logln("Should have thrown err."); 355 errln("failed, should have thrown err."); 356 } catch(MissingResourceException mre) { 357 logln("PASS: caught missing resource exception on locale 'und'"); 358 logln(mre.toString()); 359 } 360 } 361 362 @Test 363 public void TestResourceWithCalendar() { 364 DurationFormat df = DurationFormat.getInstance(new ULocale("th@calendar=buddhist")); 365 // should pass, but return a default formatter for th. 366 if (df == null) { 367 errln("FAIL: null DurationFormat returned."); 368 } 369 } 370 371 /* Tests the class 372 * DurationFormat 373 */ 374 @Test 375 public void TestDurationFormat(){ 376 @SuppressWarnings("serial") 377 class TestDurationFormat extends DurationFormat { 378 @Override 379 public StringBuffer format(Object object, StringBuffer toAppend, FieldPosition pos) {return null;} 380 @Override 381 public String formatDurationFrom(long duration, long referenceDate) {return null;} 382 @Override 383 public String formatDurationFromNow(long duration) {return null;} 384 @Override 385 public String formatDurationFromNowTo(Date targetDate) {return null;} 386 public TestDurationFormat() {super();} 387 388 } 389 390 // Tests the constructor and the following method 391 // public Object parseObject(String source, ParsePosition pos) 392 try{ 393 TestDurationFormat tdf = new TestDurationFormat(); 394 tdf.parseObject("",null); 395 errln("DurationFormat.parseObjet(String,ParsePosition) was " + 396 "to return an exception for an unsupported operation."); 397 } catch(Exception e){} 398 } 399 400 @Test 401 public void TestFromNowTo() { 402 class TestCase { 403 ULocale locale; 404 int diffInSeconds; 405 String expected; 406 TestCase(ULocale locale, int diffInSeconds, String expected) { 407 this.locale = locale; 408 this.diffInSeconds = diffInSeconds; 409 this.expected = expected; 410 } 411 } 412 TestCase[] testCases = { 413 new TestCase(ULocale.US, 10, "10 seconds from now"), 414 new TestCase(ULocale.US, -10, "10 seconds ago"), 415 new TestCase(ULocale.US, -1800, "30 minutes ago"), 416 new TestCase(ULocale.US, 3600, "1 hour from now"), 417 new TestCase(ULocale.US, 10000, "2 hours from now"), 418 new TestCase(ULocale.US, -20000, "5 hours ago"), 419 new TestCase(ULocale.FRANCE, -1800, "il y a 30 minutes"), 420 new TestCase(ULocale.ITALY, 10000, "fra due ore"), 421 }; 422 423 final long delayMS = 10; // Safe margin - 10 milliseconds 424 // See the comments below 425 for (TestCase test : testCases) { 426 DurationFormat df = DurationFormat.getInstance(test.locale); 427 long target = System.currentTimeMillis() + test.diffInSeconds * 1000; 428 // Need some adjustment because time difference is recalculated in 429 // formatDurationFromNowTo method. 430 target = test.diffInSeconds > 0 ? target + delayMS : target - delayMS; 431 Date d = new Date(target); 432 String result = df.formatDurationFromNowTo(d); 433 assertEquals("TestFromNowTo (" + test.locale + ", " + test.diffInSeconds + "sec)", 434 test.expected, result); 435 } 436 } 437 } 438