1 package net.sf.saxon.functions; 2 import net.sf.saxon.expr.ExpressionTool; 3 import net.sf.saxon.expr.StaticContext; 4 import net.sf.saxon.expr.XPathContext; 5 import net.sf.saxon.instruct.NumberInstruction; 6 import net.sf.saxon.number.Numberer; 7 import net.sf.saxon.om.Item; 8 import net.sf.saxon.om.FastStringBuffer; 9 import net.sf.saxon.trans.DynamicError; 10 import net.sf.saxon.trans.StaticError; 11 import net.sf.saxon.trans.XPathException; 12 import net.sf.saxon.type.Type; 13 import net.sf.saxon.value.*; 14 15 import javax.xml.transform.TransformerException ; 16 import java.util.Calendar ; 17 import java.util.Locale ; 18 import java.util.regex.Matcher ; 19 import java.util.regex.Pattern ; 20 21 24 25 public class FormatDate extends SystemFunction implements XSLTFunction { 26 27 public void checkArguments(StaticContext env) throws XPathException { 28 int numArgs = argument.length; 29 if (numArgs != 2 && numArgs != 5) { 30 throw new StaticError("Function " + getDisplayName(env.getNamePool()) + 31 " must have either two or five arguments", 32 ExpressionTool.getLocator(this)); 33 } 34 super.checkArguments(env); 35 } 36 37 40 41 public Item evaluateItem(XPathContext context) throws XPathException { 42 CalendarValue value = (CalendarValue)argument[0].evaluateItem(context); 43 if (value==null) { 44 return null; 45 } 46 String format = argument[1].evaluateItem(context).getStringValue(); 48 49 String language; 50 51 if (argument.length > 2) { 52 AtomicValue languageVal = (AtomicValue)argument[2].evaluateItem(context); 53 if (languageVal==null) { 56 language = Locale.getDefault().getLanguage(); 57 } else { 58 language = languageVal.getStringValue(); 59 if (language.length() >= 2) { 60 language = language.substring(0, 2); 61 } else { 62 language = Locale.getDefault().getLanguage(); 63 } 64 } 65 } else { 66 language = Locale.getDefault().getLanguage(); 67 } 68 69 return new StringValue(formatDate(value, format, language, context)); 70 } 71 72 76 77 private static CharSequence formatDate(CalendarValue value, String format, String language, XPathContext context) 78 throws XPathException { 79 80 Numberer numberer = NumberInstruction.makeNumberer(language, context); 81 FastStringBuffer sb = new FastStringBuffer(32); 82 int i = 0; 83 while (true) { 84 while (i < format.length() && format.charAt(i) != '[') { 85 sb.append(format.charAt(i)); 86 if (format.charAt(i) == ']') { 87 i++; 88 if (i == format.length() || format.charAt(i) != ']') { 89 DynamicError e = new DynamicError("Closing ']' in date picture must be written as ']]'"); 90 e.setXPathContext(context); 91 throw e; 92 } 93 } 94 i++; 95 } 96 if (i == format.length()) { 97 break; 98 } 99 i++; 101 if (format.charAt(i) == '[') { 102 sb.append('['); 103 i++; 104 } else { 105 int close = format.indexOf("]", i); 106 if (close == -1) { 107 DynamicError e = new DynamicError("Date format contains a '[' with no matching ']'"); 108 e.setXPathContext(context); 109 throw e; 110 } 111 String componentFormat = format.substring(i, close); 112 sb.append(formatComponent(value, componentFormat.trim(), numberer, context)); 113 i = close+1; 114 } 115 } 116 return sb; 117 } 118 119 private static Pattern componentPattern = 120 Pattern.compile("([YMDdWwFHhmsfZzPCE])\\s*(.*)"); 121 122 private static CharSequence formatComponent(CalendarValue value, String specifier, Numberer numberer, XPathContext context) 123 throws XPathException { 124 boolean ignoreDate = (value instanceof TimeValue); 125 boolean ignoreTime = (value instanceof DateValue); 126 DateTimeValue dtvalue; 127 if (ignoreDate) { 128 dtvalue = ((TimeValue)value).toDateTime(); 129 } else if (ignoreTime) { 130 dtvalue = (DateTimeValue)value.convert(Type.DATE_TIME, context); 131 } else { 132 dtvalue = (DateTimeValue)value; 133 } 134 Calendar cal = dtvalue.getCalendar(); 135 Matcher matcher = componentPattern.matcher(specifier); 136 if (!matcher.matches()) { 137 try { 138 context.getController().getErrorListener().warning( 139 new DynamicError("Unrecognized date/time component [" + specifier + "] (ignored)")); 140 } catch (TransformerException e) { 141 throw DynamicError.makeDynamicError(e); 142 } 143 return ""; 144 } 145 String component = matcher.group(1); 146 String format = matcher.group(2); 147 if (format==null) { 148 format = ""; 149 } 150 boolean defaultFormat = false; 151 if ("".equals(format) || format.startsWith(",")) { 152 defaultFormat = true; 153 switch (component.charAt(0) ) { 154 case 'F': 155 format = "Nn" + format; 156 break; 157 case 'P': 158 format = 'n' + format; 159 break; 160 case 'C': 161 case 'E': 162 format = 'N' + format; 163 break; 164 case 'm': 165 case 's': 166 format = "01" + format; 167 break; 168 default: 169 format = '1' + format; 170 } 171 } 172 173 switch (component.charAt(0)) { 174 case 'Y': if (ignoreDate) { 176 return ""; 177 } else { 178 return formatNumber(component, cal.get(Calendar.YEAR), format, defaultFormat, numberer, context); 179 } 180 case 'M': if (ignoreDate) { 182 return ""; 183 } else { 184 return formatNumber(component, cal.get(Calendar.MONTH)+1, format, defaultFormat, numberer, context); 185 } 186 case 'D': if (ignoreDate) { 188 return ""; 189 } else { 190 return formatNumber(component, cal.get(Calendar.DAY_OF_MONTH), format, defaultFormat, numberer, context); 191 } 192 case 'd': if (ignoreDate) { 194 return ""; 195 } else { 196 return formatNumber(component, cal.get(Calendar.DAY_OF_YEAR), format, defaultFormat, numberer, context); 197 } 198 case 'W': if (ignoreDate) { 200 return ""; 201 } else { 202 return formatNumber(component, cal.get(Calendar.WEEK_OF_YEAR), format, defaultFormat, numberer, context); 203 } 204 case 'w': if (ignoreDate) { 206 return ""; 207 } else { 208 return formatNumber(component, cal.get(Calendar.WEEK_OF_MONTH), format, defaultFormat, numberer, context); 209 } 210 case 'H': if (ignoreTime) { 212 return ""; 213 } else { 214 return formatNumber(component, cal.get(Calendar.HOUR_OF_DAY), format, defaultFormat, numberer, context); 215 } 216 case 'h': if (ignoreTime) { 218 return ""; 219 } else { 220 int hr = cal.get(Calendar.HOUR); 221 if (hr==0) hr = 12; 222 return formatNumber(component, hr, format, defaultFormat, numberer, context); 223 } 224 case 'm': if (ignoreTime) { 226 return ""; 227 } else { 228 return formatNumber(component, cal.get(Calendar.MINUTE), format, defaultFormat, numberer, context); 229 } 230 case 's': if (ignoreTime) { 232 return ""; 233 } else { 234 return formatNumber(component, cal.get(Calendar.SECOND), format, defaultFormat, numberer, context); 235 } 236 case 'f': if (ignoreTime) { 239 return ""; 240 } else { 241 int millis = cal.get(Calendar.MILLISECOND) % 1000; 242 return ((1000 + millis)+"").substring(1); 243 } 244 case 'Z': case 'z': FastStringBuffer sbz = new FastStringBuffer(8); 247 DateTimeValue.appendTimezone(cal, sbz); 248 return sbz.toString(); 249 251 case 'F': if (ignoreDate) { 253 return ""; 254 } else { 255 return formatNumber(component, cal.get(Calendar.DAY_OF_WEEK), format, defaultFormat, numberer, context); 256 } 257 case 'P': if (ignoreTime) { 259 return ""; 260 } else { 261 int hour = cal.get(Calendar.HOUR_OF_DAY); 262 int minutes = cal.get(Calendar.MINUTE); 263 return formatNumber(component, hour*60 + minutes, format, defaultFormat, numberer, context); 264 } 265 case 'C': return "Gregorian"; 267 case 'E': if (ignoreDate) { 269 return ""; 270 } else { 271 return "*AD*"; 272 } 273 default: 275 DynamicError e = new DynamicError("Unknown formatDate/time component specifier '" + format.charAt(0) + '\''); 276 e.setXPathContext(context); 277 throw e; 278 } 279 } 280 281 private static Pattern formatPattern = 282 Pattern.compile("([^ot,]*?)([ot]?)(,.*)?"); 283 284 private static Pattern widthPattern = 285 Pattern.compile(",(\\*|[0-9]+)(\\-(\\*|[0-9]+))?"); 286 287 private static CharSequence formatNumber(String component, int value, 288 String format, boolean defaultFormat, Numberer numberer, XPathContext context) 289 throws XPathException { 290 Matcher matcher = formatPattern.matcher(format); 291 if (!matcher.matches()) { 292 matcher = formatPattern.matcher("1"); 293 matcher.matches(); 294 } 295 String primary = matcher.group(1); 296 String modifier = matcher.group(2); 297 String letterValue = ("t".equals(modifier) ? "traditional" : null); 298 String ordinal = ("o".equals(modifier) ? numberer.getOrdinalSuffixForDateTime(component) : null); 299 String widths = matcher.group(3); 300 int min, max; 301 302 if (widths==null || "".equals(widths)) { 303 min = 1; 304 max = Integer.MAX_VALUE; 305 } else { 306 int[] range = getWidths(widths, context); 307 min = range[0]; 308 max = range[1]; 309 if (defaultFormat) { 310 if (primary.endsWith("1") && min != primary.length()) { 312 FastStringBuffer sb = new FastStringBuffer(min+1); 313 for (int i=1; i<min; i++) { 314 sb.append('0'); 315 } 316 sb.append('1'); 317 primary = sb.toString(); 318 } 319 } 320 } 321 322 if ("P".equals(component)) { 323 if (!("N".equals(primary) || "n".equals(primary) || "Nn".equals(primary))) { 325 primary = "n"; 326 } 327 } 328 329 if ("N".equals(primary) || "n".equals(primary) || "Nn".equals(primary)) { 330 String s = ""; 331 if ("M".equals(component)) { 332 s = numberer.monthName(value, min, max); 333 } else if ("F".equals(component)) { 334 s = numberer.dayName(value, min, max); 335 } else if ("P".equals(component)) { 336 s = numberer.halfDayName(value, min, max); 337 } else { 338 primary = "1"; 339 } 340 if ("N".equals(primary)) { 341 return s.toUpperCase(); 342 } else if ("n".equals(primary)) { 343 return s.toLowerCase(); 344 } else { 345 return s; 346 } 347 } 348 349 String s = numberer.format(value, primary, 0, ",", letterValue, ordinal); 350 351 while (s.length() < min) { 352 s = ("00000000"+s).substring(s.length()+8-min); 353 } 354 if (s.length() > max) { 355 if (component.charAt(0) == 'Y') { 357 s = s.substring(s.length() - max); 358 } 359 } 360 return s; 361 } 362 363 private static int[] getWidths(String widths, XPathContext context) throws XPathException { 364 try { 365 int min = -1; 366 int max = -1; 367 368 if (!"".equals(widths)) { 369 Matcher widthMatcher = widthPattern.matcher(widths); 370 if (widthMatcher.matches()) { 371 String smin = widthMatcher.group(1); 372 if (smin==null || "".equals(smin) || "*".equals(smin)) { 373 min = 1; 374 } else { 375 min = Integer.parseInt(smin); 376 } 377 String smax = widthMatcher.group(3); 378 if (smax==null || "".equals(smax) || "*".equals(smax)) { 379 max = Integer.MAX_VALUE; 380 } else { 381 max = Integer.parseInt(smax); 382 } 383 } else { 384 try { 385 context.getController().getErrorListener().warning( 386 new DynamicError("Invalid width specifier '" + widths + 387 "' in date/time picture (ignored)")); 388 } catch (TransformerException e) { 389 throw DynamicError.makeDynamicError(e); 390 } 391 min = 1; 392 max = 50; 393 } 394 } 395 396 if (min>max && max!=-1) { 397 DynamicError e = new DynamicError("Minimum width in date/time picture exceeds maximum width"); 398 e.setXPathContext(context); 399 throw e; 400 } 401 int[] result = new int[2]; 402 result[0] = min; 403 result[1] = max; 404 return result; 405 } catch (NumberFormatException err) { 406 DynamicError e = new DynamicError("Invalid integer used as width in date/time picture"); 407 e.setXPathContext(context); 408 throw e; 409 } 410 } 411 412 } 413 414 415 416 434 | Popular Tags |