001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.util; 029 030import org.opencms.acacia.shared.I_CmsSerialDateValue; 031import org.opencms.main.CmsLog; 032import org.opencms.util.CmsCollectionsGenericWrapper; 033 034import java.text.DateFormat; 035import java.text.SimpleDateFormat; 036import java.util.Calendar; 037import java.util.Date; 038import java.util.GregorianCalendar; 039import java.util.Locale; 040import java.util.Map; 041 042import org.apache.commons.collections.Transformer; 043import org.apache.commons.logging.Log; 044 045/** 046 * An instance of a date series with a start and optional end time, 047 * usable to describe one date for events and similar contents.<p> 048 * 049 * Provides convenient methods to format the date or date range.<p> 050 */ 051public class CmsJspInstanceDateBean { 052 053 /** Formatting options for dates. */ 054 public static class CmsDateFormatOption { 055 056 /** The date format. */ 057 SimpleDateFormat m_dateFormat; 058 /** The time format. */ 059 SimpleDateFormat m_timeFormat; 060 /** The date and time format. */ 061 SimpleDateFormat m_dateTimeFormat; 062 063 /** 064 * Create a new date format option. 065 * 066 * Examples (for date 19/06/82 11:17): 067 * <ul> 068 * <li>"dd/MM/yy" 069 * <ul> 070 * <li>formatDate: "19/06/82"</li> 071 * <li>formatTime: ""</li> 072 * <li>formatDateTime: "19/06/82"</li> 073 * </ul> 074 * </li> 075 * <li>"dd/MM/yy|hh:mm" 076 * <ul> 077 * <li>formatDate: "19/06/82"</li> 078 * <li>formatTime: "11:17"</li> 079 * <li>formatDateTime: "19/06/82 11:17"</li> 080 * </ul> 081 * </li> 082 * <li>"dd/MM/yy|hh:mm|dd/MM/yy - hh:mm" 083 * <ul> 084 * <li>formatDate: "19/06/82"</li> 085 * <li>formatTime: "11:17"</li> 086 * <li>formatDateTime: "19/06/82 - 11:17"</li> 087 * </ul> 088 * </li> 089 * @param configString the configuration string, should be structured as "datePattern|timePattern|dateTimePattern", where only datePattern is mandatory. 090 * @param locale the locale to use for printing days of week, month names etc. 091 * @throws IllegalArgumentException thrown if the configured patterns are invalid. 092 */ 093 public CmsDateFormatOption(String configString, Locale locale) 094 throws IllegalArgumentException { 095 096 if (null != configString) { 097 String[] config = configString.split("\\|"); 098 String datePattern = config[0]; 099 if (!datePattern.trim().isEmpty()) { 100 m_dateFormat = new SimpleDateFormat(datePattern, locale); 101 } 102 if (config.length > 1) { 103 String timePattern = config[1]; 104 if (!timePattern.trim().isEmpty()) { 105 m_timeFormat = new SimpleDateFormat(timePattern, locale); 106 } 107 if (config.length > 2) { 108 String dateTimePattern = config[2]; 109 if (!dateTimePattern.trim().isEmpty()) { 110 m_dateTimeFormat = new SimpleDateFormat(dateTimePattern, locale); 111 } 112 } else if ((null != m_dateFormat) && (null != m_timeFormat)) { 113 m_dateTimeFormat = new SimpleDateFormat( 114 m_dateFormat.toPattern() + " " + m_timeFormat.toPattern(), 115 locale); 116 } 117 } 118 } 119 } 120 121 /** 122 * Returns the formatted date (without time). 123 * @param d the {@link Date} to format. 124 * @return the formatted date (without time). 125 */ 126 String formatDate(Date d) { 127 128 return null != m_dateFormat ? m_dateFormat.format(d) : ""; 129 } 130 131 /** 132 * Returns the formatted date (with time). 133 * @param d the {@link Date} to format. 134 * @return the formatted date (with time). 135 */ 136 String formatDateTime(Date d) { 137 138 return null != m_dateTimeFormat 139 ? m_dateTimeFormat.format(d) 140 : null != m_dateFormat ? m_dateFormat.format(d) : m_timeFormat != null ? m_timeFormat.format(d) : ""; 141 } 142 143 /** 144 * Returns the formatted time (without date). 145 * @param d the {@link Date} to format. 146 * @return the formatted time (without date). 147 */ 148 String formatTime(Date d) { 149 150 return null != m_timeFormat ? m_timeFormat.format(d) : ""; 151 } 152 } 153 154 /** Transformer from formatting options to formatted dates. */ 155 public class CmsDateFormatTransformer implements Transformer { 156 157 /** The locale to use for formatting (e.g. for the names of month). */ 158 Locale m_locale; 159 160 /** 161 * Constructor for the date format transformer. 162 * @param locale the locale to use for writing names of month or days of weeks etc. 163 */ 164 public CmsDateFormatTransformer(Locale locale) { 165 166 m_locale = locale; 167 } 168 169 /** 170 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 171 */ 172 public String transform(Object formatOption) { 173 174 CmsDateFormatOption option = null; 175 try { 176 option = new CmsDateFormatOption(formatOption.toString(), m_locale); 177 } catch (IllegalArgumentException e) { 178 LOG.error( 179 "At least one of the provided date/time patterns are illegal. Defaulting to short default date format.", 180 e); 181 } 182 return getFormattedDate(option); 183 } 184 185 } 186 187 /** The log object for this class. */ 188 static final Log LOG = CmsLog.getLog(CmsJspInstanceDateBean.class); 189 190 /** The separator between start and end date to use when formatting dates. */ 191 private static final String DATE_SEPARATOR = " - "; 192 193 /** Beginning of this instance date. */ 194 private Date m_start; 195 196 /** End of this instance date. */ 197 private Date m_end; 198 199 /** Explicitely set end of the instance date. */ 200 private Date m_explicitEnd; 201 202 /** Indicates if this instance date explicitely lasts the whole day. */ 203 private Boolean m_explicitWholeDay; 204 205 /** Explicitely set locale of this instance date. */ 206 private Locale m_explicitLocale; 207 208 /** The series this instance date is part of. */ 209 private CmsJspDateSeriesBean m_series; 210 211 /** The dates of this instance date formatted locale specific in long style. */ 212 private String m_formatLong; 213 214 /** The dates of this instance date formatted locale specific in short style. */ 215 private String m_formatShort; 216 217 /** The formatted dates as lazy map. */ 218 private Map<String, String> m_formattedDates; 219 220 /** 221 * Empty Constructor, for use as JavaBean.<p> 222 * 223 * Requires to call one of the init() methods later.<p> 224 * 225 * @see #init(Date, Locale) 226 */ 227 public CmsJspInstanceDateBean() { 228 229 // noop 230 } 231 232 /** 233 * Constructor taking start and the date series this instance date belongs to.<p> 234 * 235 * @param start the start date for this instance date 236 * @param series the date series this instance date belongs to 237 */ 238 public CmsJspInstanceDateBean(Date start, CmsJspDateSeriesBean series) { 239 240 m_start = start; 241 m_series = series; 242 } 243 244 /** 245 * Constructor to wrap a single date as instance date.<p> 246 * 247 * This will allow to use the format options.<p> 248 * 249 * @param start the start date for this instance date 250 * @param locale the locale used to format the date 251 * 252 */ 253 public CmsJspInstanceDateBean(Date start, Locale locale) { 254 255 this(start, new CmsJspDateSeriesBean(Long.toString(start.getTime()), locale)); 256 } 257 258 /** 259 * Returns the end time of this instance date.<p> 260 * 261 * @return the end time of this instance date 262 */ 263 public Date getEnd() { 264 265 if (m_explicitEnd != null) { 266 return isWholeDay() ? adjustForWholeDay(m_explicitEnd, true) : m_explicitEnd; 267 } 268 if ((m_end == null) && (m_series.getInstanceDuration() != null)) { 269 m_end = new Date(m_start.getTime() + m_series.getInstanceDuration().longValue()); 270 } 271 if (m_end == null) { 272 m_end = new Date(getStart().getTime()); 273 } 274 return (m_end.getTime() > 0) && isWholeDay() && !m_series.isWholeDay() ? adjustForWholeDay(m_end, true) : m_end; 275 } 276 277 /** 278 * Returns an instance date bean wrapping only the end date of the original bean. 279 * @return an instance date bean wrapping only the end date of the original bean. 280 */ 281 public CmsJspInstanceDateBean getEndInstance() { 282 283 return new CmsJspInstanceDateBean(getEnd(), m_series.getLocale()); 284 } 285 286 /** 287 * Returns a lazy map from date format options to dates. 288 * Supported formats are the values of {@link CmsDateFormatOption}.<p> 289 * 290 * Each option must be backed up by four three keys in the message "bundle org.opencms.jsp.util.messages" for you locale: 291 * GUI_PATTERN_DATE_{Option}, GUI_PATTERN_DATE_TIME_{Option} and GUI_PATTERN_TIME_{Option}. 292 * 293 * @return a lazy map from date patterns to dates. 294 */ 295 public Map<String, String> getFormat() { 296 297 if (null == m_formattedDates) { 298 m_formattedDates = CmsCollectionsGenericWrapper.createLazyMap( 299 new CmsDateFormatTransformer(m_series.getLocale())); 300 } 301 return m_formattedDates; 302 303 } 304 305 /** 306 * Returns the start and end dates/times as "start - end" in long date format and short time format specific for the request locale. 307 * @return the formatted date/time string. 308 */ 309 public String getFormatLong() { 310 311 if (m_formatLong == null) { 312 m_formatLong = getFormattedDate(DateFormat.LONG); 313 } 314 return m_formatLong; 315 } 316 317 /** 318 * Returns the start and end dates/times as "start - end" in short date/time format specific for the request locale. 319 * @return the formatted date/time string. 320 */ 321 public String getFormatShort() { 322 323 if (m_formatShort == null) { 324 m_formatShort = getFormattedDate(DateFormat.SHORT); 325 } 326 return m_formatShort; 327 } 328 329 /** 330 * Checks if this instance date has been set or initialized.<p> 331 * 332 * If the start date of the instance date is 0 milliseconds, we assume the instance date has not been set.<p> 333 * 334 * @return true if this instance date has been set or initialized 335 */ 336 public boolean getIsSet() { 337 338 return (m_start != null) && (m_start.getTime() != 0); 339 } 340 341 /** 342 * Returns a time of the last day where this instance date takes place.<p> 343 * 344 * This can be used to output the last calendar day of this instance date without time.<p> 345 * 346 * @return a time of the last day where this instance date takes place 347 */ 348 public Date getLastDay() { 349 350 // for whole day instance dates the end date is adjusted by subtracting one day, 351 // otherwise the period would be one day too long 352 return isWholeDay() ? new Date(getEnd().getTime() - I_CmsSerialDateValue.DAY_IN_MILLIS) : getEnd(); 353 } 354 355 /** 356 * Returns the start time of this instance date.<p> 357 * 358 * @return the start time of this instance date 359 */ 360 public Date getStart() { 361 362 // Adjust the start time for an explicitely whole day option that overwrites the series' whole day option. 363 if (m_start == null) { 364 m_start = new Date(0); 365 } 366 return (m_start.getTime() > 0) && isWholeDay() && !m_series.isWholeDay() 367 ? adjustForWholeDay(m_start, false) 368 : m_start; 369 } 370 371 /** 372 * Returns an instance date bean wrapping only the start date of the original bean.<p> 373 * 374 * @return an instance date bean wrapping only the start date of the original bean 375 */ 376 public CmsJspInstanceDateBean getStartInstance() { 377 378 return new CmsJspInstanceDateBean(getStart(), m_series.getLocale()); 379 } 380 381 /** 382 * Initializes this date instance.<p> 383 * 384 * Use this only in case this date instance has been created as a JavaBean.<p> 385 * 386 * @param start the start date for this instance date 387 * @param locale the locale used to format the date 388 */ 389 public void init(Date start, Locale locale) { 390 391 m_start = start; 392 m_series = new CmsJspDateSeriesBean(Long.toString(start.getTime()), locale); 393 } 394 395 /** 396 * Initializes this date instance with a String for the locale.<p> 397 * 398 * Use this only in case this date instance has been created as a JavaBean.<p> 399 * 400 * @param start the start date for this instance date 401 * @param localeStr a String representing the locale used to format the date 402 */ 403 public void init(Date start, String localeStr) { 404 405 init(start, new Locale(localeStr)); 406 } 407 408 /** 409 * Returns a flag, indicating if this instance date last over night.<p> 410 * 411 * @return <code>true</code> if this instance date ends on another day than it starts, <code>false</code> if it ends on the same day. 412 */ 413 public boolean isMultiDay() { 414 415 if ((null != m_explicitEnd) || (null != m_explicitWholeDay)) { 416 return isSingleMultiDay(); 417 } else { 418 return m_series.isMultiDay(); 419 } 420 } 421 422 /** 423 * Indicates if this instance date lasts whole days.<p> 424 * 425 * @return true if this instance date lasts whole days 426 */ 427 public boolean isWholeDay() { 428 429 return null == m_explicitWholeDay ? m_series.isWholeDay() : m_explicitWholeDay.booleanValue(); 430 } 431 432 /** 433 * Explicitly set the end time of this instance date.<p> 434 * 435 * If the provided date is <code>null</code> or a date before the start date, the end date defaults to the start date. 436 * 437 * @param endDate the end time of this instance date 438 */ 439 public void setEnd(Date endDate) { 440 441 if ((null == endDate) || getStart().after(endDate)) { 442 m_explicitEnd = null; 443 } else { 444 m_explicitEnd = endDate; 445 } 446 } 447 448 /** 449 * Explicitly set the end time of this instance date using a long value.<p> 450 * 451 * If the provided date is <code>null</code> or a date before the start date, the end date defaults to the start date. 452 * 453 * @param endDate the end time of this instance date 454 */ 455 public void setEnd(long endDate) { 456 457 m_formatLong = null; 458 m_formatShort = null; 459 setEnd(new Date(endDate)); 460 } 461 462 /** 463 * Set if this instance date is whole day. 464 * 465 * @param isWholeDay flag, indicating if this instance date lasts the whole day - 466 * if <code>null</code> the value defaults to the setting from the underlying date series. 467 */ 468 public void setWholeDay(Boolean isWholeDay) { 469 470 m_formatLong = null; 471 m_formatShort = null; 472 m_explicitWholeDay = isWholeDay; 473 } 474 475 /** 476 * Returns the start and end dates/times as "start - end" in the provided date/time format specific for the request locale. 477 * @param formatOption the format to use for date and time. 478 * @return the formatted date/time string. 479 */ 480 String getFormattedDate(CmsDateFormatOption formatOption) { 481 482 if (null == formatOption) { 483 return getFormattedDate(DateFormat.SHORT); 484 } 485 String result; 486 if (isWholeDay()) { 487 result = formatOption.formatDate(getStart()); 488 if (getLastDay().after(getStart())) { 489 String to = formatOption.formatDate(getLastDay()); 490 if (!to.isEmpty()) { 491 result += DATE_SEPARATOR + to; 492 } 493 } 494 } else { 495 result = formatOption.formatDateTime(getStart()); 496 if (getEnd().after(getStart())) { 497 String to; 498 if (isMultiDay()) { 499 to = formatOption.formatDateTime(getEnd()); 500 } else { 501 to = formatOption.formatTime(getEnd()); 502 } 503 if (!to.isEmpty()) { 504 result += DATE_SEPARATOR + to; 505 } 506 } 507 } 508 509 return result; 510 } 511 512 /** 513 * Adjust the date according to the whole day options.<p> 514 * 515 * @param date the date to adjust 516 * @param isEnd true if the date is the end of this instance date (in contrast to the beginning) 517 * 518 * @return the adjusted date, which will be exactly the beginning or the end of the provide date's day 519 */ 520 private Date adjustForWholeDay(Date date, boolean isEnd) { 521 522 Calendar result = new GregorianCalendar(); 523 result.setTime(date); 524 result.set(Calendar.HOUR_OF_DAY, 0); 525 result.set(Calendar.MINUTE, 0); 526 result.set(Calendar.SECOND, 0); 527 result.set(Calendar.MILLISECOND, 0); 528 if (isEnd) { 529 result.add(Calendar.DATE, 1); 530 } 531 532 return result.getTime(); 533 } 534 535 /** 536 * Returns the start and end dates/times as "start - end" in the provided date/time format specific for the request locale.<p> 537 * 538 * @param dateTimeFormat the format to use for date (time is always short) 539 * @return the formatted date/time string 540 */ 541 private String getFormattedDate(int dateTimeFormat) { 542 543 DateFormat df; 544 String result; 545 if (isWholeDay()) { 546 df = DateFormat.getDateInstance(dateTimeFormat, m_series.getLocale()); 547 result = df.format(getStart()); 548 if (getLastDay().after(getStart())) { 549 result += DATE_SEPARATOR + df.format(getLastDay()); 550 } 551 } else { 552 df = DateFormat.getDateTimeInstance(dateTimeFormat, DateFormat.SHORT, m_series.getLocale()); 553 result = df.format(getStart()); 554 if (getEnd().after(getStart())) { 555 if (isMultiDay()) { 556 result += DATE_SEPARATOR + df.format(getEnd()); 557 } else { 558 df = DateFormat.getTimeInstance(DateFormat.SHORT, m_series.getLocale()); 559 result += DATE_SEPARATOR + df.format(getEnd()); 560 } 561 } 562 } 563 564 return result; 565 } 566 567 /** 568 * Returns a flag, indicating if this instance date is multi-day.<p> 569 * 570 * The method is only called if this instance date has an explicitely set end date 571 * or an explicitely changed whole day option.<p> 572 * 573 * @return true if this instance date is multi-day 574 */ 575 private boolean isSingleMultiDay() { 576 577 long duration = getEnd().getTime() - getStart().getTime(); 578 if (duration > I_CmsSerialDateValue.DAY_IN_MILLIS) { 579 return true; 580 } 581 if (isWholeDay() && (duration <= I_CmsSerialDateValue.DAY_IN_MILLIS)) { 582 return false; 583 } 584 Calendar start = new GregorianCalendar(); 585 start.setTime(getStart()); 586 Calendar end = new GregorianCalendar(); 587 end.setTime(getEnd()); 588 if (start.get(Calendar.DAY_OF_MONTH) == end.get(Calendar.DAY_OF_MONTH)) { 589 return false; 590 } 591 return true; 592 593 } 594}