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/** Bean for easy access to information for single events. */ 046public class CmsJspInstanceDateBean { 047 048 /** Formatting options for dates. */ 049 public static class CmsDateFormatOption { 050 051 /** The date format. */ 052 SimpleDateFormat m_dateFormat; 053 /** The time format. */ 054 SimpleDateFormat m_timeFormat; 055 /** The date and time format. */ 056 SimpleDateFormat m_dateTimeFormat; 057 058 /** 059 * Create a new date format option. 060 * 061 * Examples (for date 19/06/82 11:17): 062 * <ul> 063 * <li>"dd/MM/yy" 064 * <ul> 065 * <li>formatDate: "19/06/82"</li> 066 * <li>formatTime: ""</li> 067 * <li>formatDateTime: "19/06/82"</li> 068 * </ul> 069 * </li> 070 * <li>"dd/MM/yy|hh:mm" 071 * <ul> 072 * <li>formatDate: "19/06/82"</li> 073 * <li>formatTime: "11:17"</li> 074 * <li>formatDateTime: "19/06/82 11:17"</li> 075 * </ul> 076 * </li> 077 * <li>"dd/MM/yy|hh:mm|dd/MM/yy - hh:mm" 078 * <ul> 079 * <li>formatDate: "19/06/82"</li> 080 * <li>formatTime: "11:17"</li> 081 * <li>formatDateTime: "19/06/82 - 11:17"</li> 082 * </ul> 083 * </li> 084 * @param configString the configuration string, should be structured as "datePattern|timePattern|dateTimePattern", where only datePattern is mandatory. 085 * @param locale the locale to use for printing days of week, month names etc. 086 * @throws IllegalArgumentException thrown if the configured patterns are invalid. 087 */ 088 public CmsDateFormatOption(String configString, Locale locale) 089 throws IllegalArgumentException { 090 091 if (null != configString) { 092 String[] config = configString.split("\\|"); 093 String datePattern = config[0]; 094 if (!datePattern.trim().isEmpty()) { 095 m_dateFormat = new SimpleDateFormat(datePattern, locale); 096 } 097 if (config.length > 1) { 098 String timePattern = config[1]; 099 if (!timePattern.trim().isEmpty()) { 100 m_timeFormat = new SimpleDateFormat(timePattern, locale); 101 } 102 if (config.length > 2) { 103 String dateTimePattern = config[2]; 104 if (!dateTimePattern.trim().isEmpty()) { 105 m_dateTimeFormat = new SimpleDateFormat(dateTimePattern, locale); 106 } 107 } else if ((null != m_dateFormat) && (null != m_timeFormat)) { 108 m_dateTimeFormat = new SimpleDateFormat( 109 m_dateFormat.toPattern() + " " + m_timeFormat.toPattern(), 110 locale); 111 } 112 } 113 } 114 } 115 116 /** 117 * Returns the formatted date (without time). 118 * @param d the {@link Date} to format. 119 * @return the formatted date (without time). 120 */ 121 String formatDate(Date d) { 122 123 return null != m_dateFormat ? m_dateFormat.format(d) : ""; 124 } 125 126 /** 127 * Returns the formatted date (with time). 128 * @param d the {@link Date} to format. 129 * @return the formatted date (with time). 130 */ 131 String formatDateTime(Date d) { 132 133 return null != m_dateTimeFormat 134 ? m_dateTimeFormat.format(d) 135 : null != m_dateFormat ? m_dateFormat.format(d) : m_timeFormat != null ? m_timeFormat.format(d) : ""; 136 } 137 138 /** 139 * Returns the formatted time (without date). 140 * @param d the {@link Date} to format. 141 * @return the formatted time (without date). 142 */ 143 String formatTime(Date d) { 144 145 return null != m_timeFormat ? m_timeFormat.format(d) : ""; 146 } 147 } 148 149 /** Transformer from formatting options to formatted dates. */ 150 public class CmsDateFormatTransformer implements Transformer { 151 152 /** The locale to use for formatting (e.g. for the names of month). */ 153 Locale m_locale; 154 155 /** 156 * Constructor for the date format transformer. 157 * @param locale the locale to use for writing names of month or days of weeks etc. 158 */ 159 public CmsDateFormatTransformer(Locale locale) { 160 161 m_locale = locale; 162 } 163 164 /** 165 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 166 */ 167 public String transform(Object formatOption) { 168 169 CmsDateFormatOption option = null; 170 try { 171 option = new CmsDateFormatOption(formatOption.toString(), m_locale); 172 } catch (IllegalArgumentException e) { 173 LOG.error( 174 "At least one of the provided date/time patterns are illegal. Defaulting to short default date format.", 175 e); 176 } 177 return getFormattedDate(option); 178 } 179 180 } 181 182 /** The log object for this class. */ 183 static final Log LOG = CmsLog.getLog(CmsJspInstanceDateBean.class); 184 185 /** The separator between start and end date to use when formatting dates. */ 186 private static final String DATE_SEPARATOR = " - "; 187 188 /** Beginning of the event. */ 189 private Date m_start; 190 191 /** End of the event. */ 192 private Date m_end; 193 194 /** Explicitely set end of the single event. */ 195 private Date m_explicitEnd; 196 197 /** Flag, indicating if the single event explicitely lasts the whole day. */ 198 private Boolean m_explicitWholeDay; 199 200 /** The series the event is part of. */ 201 private CmsJspDateSeriesBean m_series; 202 203 /** The dates of the event formatted locale specific in long style. */ 204 private String m_formatLong; 205 206 /** The dates of the event formatted locale specific in short style. */ 207 private String m_formatShort; 208 209 /** The formatted dates as lazy map. */ 210 private Map<String, String> m_formattedDates; 211 212 /** Constructor taking start and end time for the single event. 213 * @param start the start time of the event. 214 * @param series the series, the event is part of. 215 */ 216 public CmsJspInstanceDateBean(Date start, CmsJspDateSeriesBean series) { 217 218 m_start = start; 219 m_series = series; 220 } 221 222 /** 223 * Constructor to wrap a single date as instance date. 224 * This will allow to use the format options. 225 * 226 * @param date the date to wrap 227 * @param locale the locale to use for formatting the date. 228 * 229 */ 230 public CmsJspInstanceDateBean(Date date, Locale locale) { 231 232 this(date, new CmsJspDateSeriesBean(Long.toString(date.getTime()), locale)); 233 } 234 235 /** 236 * Returns the end time of the event. 237 * @return the end time of the event. 238 */ 239 public Date getEnd() { 240 241 if (null != m_explicitEnd) { 242 return isWholeDay() ? adjustForWholeDay(m_explicitEnd, true) : m_explicitEnd; 243 } 244 if ((null == m_end) && (m_series.getInstanceDuration() != null)) { 245 m_end = new Date(m_start.getTime() + m_series.getInstanceDuration().longValue()); 246 } 247 return isWholeDay() && !m_series.isWholeDay() ? adjustForWholeDay(m_end, true) : m_end; 248 } 249 250 /** 251 * Returns an instance date bean wrapping only the end date of the original bean. 252 * @return an instance date bean wrapping only the end date of the original bean. 253 */ 254 public CmsJspInstanceDateBean getEndInstance() { 255 256 return new CmsJspInstanceDateBean(getEnd(), m_series.getLocale()); 257 } 258 259 /** 260 * Returns a lazy map from date format options to dates. 261 * Supported formats are the values of {@link CmsDateFormatOption}.<p> 262 * 263 * Each option must be backed up by four three keys in the message "bundle org.opencms.jsp.util.messages" for you locale: 264 * GUI_PATTERN_DATE_{Option}, GUI_PATTERN_DATE_TIME_{Option} and GUI_PATTERN_TIME_{Option}. 265 * 266 * @return a lazy map from date patterns to dates. 267 */ 268 public Map<String, String> getFormat() { 269 270 if (null == m_formattedDates) { 271 m_formattedDates = CmsCollectionsGenericWrapper.createLazyMap( 272 new CmsDateFormatTransformer(m_series.getLocale())); 273 } 274 return m_formattedDates; 275 276 } 277 278 /** 279 * Returns the start and end dates/times as "start - end" in long date format and short time format specific for the request locale. 280 * @return the formatted date/time string. 281 */ 282 public String getFormatLong() { 283 284 if (m_formatLong == null) { 285 m_formatLong = getFormattedDate(DateFormat.LONG); 286 } 287 return m_formatLong; 288 } 289 290 /** 291 * Returns the start and end dates/times as "start - end" in short date/time format specific for the request locale. 292 * @return the formatted date/time string. 293 */ 294 public String getFormatShort() { 295 296 if (m_formatShort == null) { 297 m_formatShort = getFormattedDate(DateFormat.SHORT); 298 } 299 return m_formatShort; 300 } 301 302 /** 303 * Returns some time of the last day, the event takes place. </p> 304 * 305 * For whole day events the end date is adjusted by subtracting one day, 306 * since it would otherwise be the 12 am of the first day, the event does not take place anymore. 307 * 308 * @return some time of the last day, the event takes place. 309 */ 310 public Date getLastDay() { 311 312 return isWholeDay() ? new Date(getEnd().getTime() - I_CmsSerialDateValue.DAY_IN_MILLIS) : getEnd(); 313 } 314 315 /** 316 * Returns the start time of the event. 317 * @return the start time of the event. 318 */ 319 public Date getStart() { 320 321 // Adjust the start time for an explicitely whole day option that overwrites the series' whole day option. 322 return isWholeDay() && !m_series.isWholeDay() ? adjustForWholeDay(m_start, false) : m_start; 323 } 324 325 /** 326 * Returns an instance date bean wrapping only the start date of the original bean. 327 * @return an instance date bean wrapping only the start date of the original bean. 328 */ 329 public CmsJspInstanceDateBean getStartInstance() { 330 331 return new CmsJspInstanceDateBean(getStart(), m_series.getLocale()); 332 } 333 334 /** 335 * Returns a flag, indicating if the event last over night. 336 * @return <code>true</code> if the event ends on another day than it starts, <code>false</code> if it ends on the same day. 337 */ 338 public boolean isMultiDay() { 339 340 if ((null != m_explicitEnd) || (null != m_explicitWholeDay)) { 341 return isSingleMultiDay(); 342 } else { 343 return m_series.isMultiDay(); 344 } 345 } 346 347 /** 348 * Returns a flag, indicating if the event lasts whole days. 349 * @return a flag, indicating if the event lasts whole days. 350 */ 351 public boolean isWholeDay() { 352 353 return null == m_explicitWholeDay ? m_series.isWholeDay() : m_explicitWholeDay.booleanValue(); 354 } 355 356 /** 357 * Explicitly set the end time of the event. 358 * 359 * If the provided date is <code>null</code> or a date before the start date, the end date defaults to the start date. 360 * 361 * @param endDate the end time of the event. 362 */ 363 public void setEnd(Date endDate) { 364 365 if ((null == endDate) || getStart().after(endDate)) { 366 m_explicitEnd = null; 367 } else { 368 m_explicitEnd = endDate; 369 } 370 } 371 372 /** 373 * Explicitly set if the single event is whole day. 374 * 375 * @param isWholeDay flag, indicating if the single event lasts the whole day. 376 * If <code>null</code> the value defaults to the setting from the underlying date series. 377 */ 378 public void setWholeDay(Boolean isWholeDay) { 379 380 m_explicitWholeDay = isWholeDay; 381 } 382 383 /** 384 * Returns the start and end dates/times as "start - end" in the provided date/time format specific for the request locale. 385 * @param formatOption the format to use for date and time. 386 * @return the formatted date/time string. 387 */ 388 String getFormattedDate(CmsDateFormatOption formatOption) { 389 390 if (null == formatOption) { 391 return getFormattedDate(DateFormat.SHORT); 392 } 393 String result; 394 if (isWholeDay()) { 395 result = formatOption.formatDate(getStart()); 396 if (getLastDay().after(getStart())) { 397 String to = formatOption.formatDate(getLastDay()); 398 if (!to.isEmpty()) { 399 result += DATE_SEPARATOR + to; 400 } 401 } 402 } else { 403 result = formatOption.formatDateTime(getStart()); 404 if (getEnd().after(getStart())) { 405 String to; 406 if (isMultiDay()) { 407 to = formatOption.formatDateTime(getEnd()); 408 } else { 409 to = formatOption.formatTime(getEnd()); 410 } 411 if (!to.isEmpty()) { 412 result += DATE_SEPARATOR + to; 413 } 414 } 415 } 416 417 return result; 418 } 419 420 /** 421 * Adjust the date according to the whole day options. 422 * 423 * @param date the date to adjust. 424 * @param isEnd flag, indicating if the date is the end of the event (in contrast to the beginning) 425 * 426 * @return the adjusted date, which will be exactly the beginning or the end of the provide date's day. 427 */ 428 private Date adjustForWholeDay(Date date, boolean isEnd) { 429 430 Calendar result = new GregorianCalendar(); 431 result.setTime(date); 432 result.set(Calendar.HOUR_OF_DAY, 0); 433 result.set(Calendar.MINUTE, 0); 434 result.set(Calendar.SECOND, 0); 435 result.set(Calendar.MILLISECOND, 0); 436 if (isEnd) { 437 result.add(Calendar.DATE, 1); 438 } 439 440 return result.getTime(); 441 } 442 443 /** 444 * Returns the start and end dates/times as "start - end" in the provided date/time format specific for the request locale. 445 * @param dateTimeFormat the format to use for date (time is always short). 446 * @return the formatted date/time string. 447 */ 448 private String getFormattedDate(int dateTimeFormat) { 449 450 DateFormat df; 451 String result; 452 if (isWholeDay()) { 453 df = DateFormat.getDateInstance(dateTimeFormat, m_series.getLocale()); 454 result = df.format(getStart()); 455 if (getLastDay().after(getStart())) { 456 result += DATE_SEPARATOR + df.format(getLastDay()); 457 } 458 } else { 459 df = DateFormat.getDateTimeInstance(dateTimeFormat, DateFormat.SHORT, m_series.getLocale()); 460 result = df.format(getStart()); 461 if (getEnd().after(getStart())) { 462 if (isMultiDay()) { 463 result += DATE_SEPARATOR + df.format(getEnd()); 464 } else { 465 df = DateFormat.getTimeInstance(DateFormat.SHORT, m_series.getLocale()); 466 result += DATE_SEPARATOR + df.format(getEnd()); 467 } 468 } 469 } 470 471 return result; 472 } 473 474 /** 475 * Returns a flag, indicating if the current event is a multi-day event. 476 * The method is only called if the single event has an explicitely set end date 477 * or an explicitely changed whole day option. 478 * 479 * @return a flag, indicating if the current event takes lasts over more than one day. 480 */ 481 private boolean isSingleMultiDay() { 482 483 long duration = getEnd().getTime() - getStart().getTime(); 484 if (duration > I_CmsSerialDateValue.DAY_IN_MILLIS) { 485 return true; 486 } 487 if (isWholeDay() && (duration <= I_CmsSerialDateValue.DAY_IN_MILLIS)) { 488 return false; 489 } 490 Calendar start = new GregorianCalendar(); 491 start.setTime(getStart()); 492 Calendar end = new GregorianCalendar(); 493 end.setTime(getEnd()); 494 if (start.get(Calendar.DAY_OF_MONTH) == end.get(Calendar.DAY_OF_MONTH)) { 495 return false; 496 } 497 return true; 498 499 } 500}