001/* 002 * $Id: CalendarUtils.java 3916 2011-01-12 10:21:58Z kleopatra $ 003 * 004 * Copyright 2007 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 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 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021package org.jdesktop.swingx.calendar; 022 023import java.util.Calendar; 024import java.util.Date; 025 026/** 027 * Calendar manipulation. 028 * 029 * PENDING: replace by something tested - as is c&p'ed dateUtils 030 * to work on a calendar instead of using long 031 * 032 * @author Jeanette Winzenburg 033 */ 034public class CalendarUtils { 035 036 // Constants used internally; unit is milliseconds 037 @SuppressWarnings("unused") 038 public static final int ONE_MINUTE = 60*1000; 039 @SuppressWarnings("unused") 040 public static final int ONE_HOUR = 60*ONE_MINUTE; 041 @SuppressWarnings("unused") 042 public static final int THREE_HOURS = 3 * ONE_HOUR; 043 @SuppressWarnings("unused") 044 public static final int ONE_DAY = 24*ONE_HOUR; 045 046 public static final int DECADE = 5467; 047 public static final int YEAR_IN_DECADE = DECADE + 1; 048 049 /** 050 * Increments the calendar field of the given calendar by amount. 051 * 052 * @param calendar 053 * @param field the field to increment, allowed are all fields known to 054 * Calendar plus DECADE. 055 * @param amount 056 * 057 * @throws IllegalArgumentException 058 */ 059 public static void add(Calendar calendar, int field, int amount) { 060 if (isNativeField(field)) { 061 calendar.add(field, amount); 062 } else { 063 switch (field) { 064 case DECADE: 065 calendar.add(Calendar.YEAR, amount * 10); 066 break; 067 default: 068 throw new IllegalArgumentException("unsupported field: " + field); 069 } 070 071 } 072 } 073 074 /** 075 * Gets the calendar field of the given calendar by amount. 076 * 077 * @param calendar 078 * @param field the field to get, allowed are all fields known to 079 * Calendar plus DECADE. 080 * 081 * @throws IllegalArgumentException 082 */ 083 public static int get(Calendar calendar, int field) { 084 if (isNativeField(field)) { 085 return calendar.get(field); 086 } 087 switch (field) { 088 case DECADE: 089 return decade(calendar.get(Calendar.YEAR)); 090 case YEAR_IN_DECADE: 091 return calendar.get(Calendar.YEAR) % 10; 092 default: 093 throw new IllegalArgumentException("unsupported field: " + field); 094 } 095 } 096 097 /** 098 * Sets the calendar field of the given calendar by amount. <p> 099 * 100 * NOTE: the custom field implementations are very naive (JSR-310 will do better) 101 * - for decade: value must be positive, value must be a multiple of 10 and is interpreted as the 102 * first-year-of-the-decade 103 * - for year-in-decade: value is added/substracted to/from the start-of-decade of the 104 * date of the given calendar 105 * 106 * @param calendar 107 * @param field the field to increment, allowed are all fields known to 108 * Calendar plus DECADE. 109 * @param value the decade to set, must be a 110 * 111 * @throws IllegalArgumentException if the field is unsupported or the value is 112 * not dividable by 10 or negative. 113 */ 114 public static void set(Calendar calendar, int field, int value) { 115 if (isNativeField(field)) { 116 calendar.set(field, value); 117 } else { 118 switch (field) { 119 case DECADE: 120 if(value <= 0 ) { 121 throw new IllegalArgumentException("value must be a positive but was: " + value); 122 } 123 if (value % 10 != 0) { 124 throw new IllegalArgumentException("value must be a multiple of 10 but was: " + value); 125 } 126 int yearInDecade = get(calendar, YEAR_IN_DECADE); 127 calendar.set(Calendar.YEAR, value + yearInDecade); 128 break; 129 case YEAR_IN_DECADE: 130 int decade = get(calendar, DECADE); 131 calendar.set(Calendar.YEAR, value + decade); 132 break; 133 default: 134 throw new IllegalArgumentException("unsupported field: " + field); 135 } 136 137 } 138 } 139 140 /** 141 * @param calendarField 142 * @return 143 */ 144 private static boolean isNativeField(int calendarField) { 145 return calendarField < DECADE; 146 } 147 148 /** 149 * Adjusts the Calendar to the end of the day of the last day in DST in the 150 * current year or unchanged if not using DST. Returns the calendar's date or null, if not 151 * using DST.<p> 152 * 153 * 154 * @param calendar the calendar to adjust 155 * @return the end of day of the last day in DST, or null if not using DST. 156 */ 157 public static Date getEndOfDST(Calendar calendar) { 158 if (!calendar.getTimeZone().useDaylightTime()) return null; 159 long old = calendar.getTimeInMillis(); 160 calendar.set(Calendar.MONTH, Calendar.DECEMBER); 161 endOfMonth(calendar); 162 startOfDay(calendar); 163 for (int i = 0; i < 366; i++) { 164 calendar.add(Calendar.DATE, -1); 165 if (calendar.getTimeZone().inDaylightTime(calendar.getTime())) { 166 endOfDay(calendar); 167 return calendar.getTime(); 168 } 169 } 170 calendar.setTimeInMillis(old); 171 return null; 172 } 173 174 /** 175 * Adjusts the Calendar to the end of the day of the first day in DST in the 176 * current year or unchanged if not using DST. Returns the calendar's date or null, if not 177 * using DST.<p> 178 * 179 * Note: the start of the day of the first day in DST is ill-defined! 180 * 181 * @param calendar the calendar to adjust 182 * @return the start of day of the first day in DST, or null if not using DST. 183 */ 184 public static Date getStartOfDST(Calendar calendar) { 185 if (!calendar.getTimeZone().useDaylightTime()) return null; 186 long old = calendar.getTimeInMillis(); 187 calendar.set(Calendar.MONTH, Calendar.JANUARY); 188 startOfMonth(calendar); 189 endOfDay(calendar); 190 for (int i = 0; i < 366; i++) { 191 calendar.add(Calendar.DATE, 1); 192 if (calendar.getTimeZone().inDaylightTime(calendar.getTime())) { 193 endOfDay(calendar); 194 return calendar.getTime(); 195 } 196 } 197 calendar.setTimeInMillis(old); 198 return null; 199 } 200 201 /** 202 * Returns a boolean indicating if the given calendar represents the 203 * start of a day (in the calendar's time zone). The calendar is unchanged. 204 * 205 * @param calendar the calendar to check. 206 * 207 * @return true if the calendar's time is the start of the day, 208 * false otherwise. 209 */ 210 public static boolean isStartOfDay(Calendar calendar) { 211 Calendar temp = (Calendar) calendar.clone(); 212 temp.add(Calendar.MILLISECOND, -1); 213 return temp.get(Calendar.DATE) != calendar.get(Calendar.DATE); 214 } 215 216 /** 217 * Returns a boolean indicating if the given calendar represents the 218 * end of a day (in the calendar's time zone). The calendar is unchanged. 219 * 220 * @param calendar the calendar to check. 221 * 222 * @return true if the calendar's time is the end of the day, 223 * false otherwise. 224 */ 225 public static boolean isEndOfDay(Calendar calendar) { 226 Calendar temp = (Calendar) calendar.clone(); 227 temp.add(Calendar.MILLISECOND, 1); 228 return temp.get(Calendar.DATE) != calendar.get(Calendar.DATE); 229 } 230 231 /** 232 * Returns a boolean indicating if the given calendar represents the 233 * start of a month (in the calendar's time zone). Returns true, if the time is 234 * the start of the first day of the month, false otherwise. The calendar is unchanged. 235 * 236 * @param calendar the calendar to check. 237 * 238 * @return true if the calendar's time is the start of the first day of the month, 239 * false otherwise. 240 */ 241 public static boolean isStartOfMonth(Calendar calendar) { 242 Calendar temp = (Calendar) calendar.clone(); 243 temp.add(Calendar.MILLISECOND, -1); 244 return temp.get(Calendar.MONTH) != calendar.get(Calendar.MONTH); 245 } 246 247 /** 248 * Returns a boolean indicating if the given calendar represents the 249 * end of a month (in the calendar's time zone). Returns true, if the time is 250 * the end of the last day of the month, false otherwise. The calendar is unchanged. 251 * 252 * @param calendar the calendar to check. 253 * 254 * @return true if the calendar's time is the end of the last day of the month, 255 * false otherwise. 256 */ 257 public static boolean isEndOfMonth(Calendar calendar) { 258 Calendar temp = (Calendar) calendar.clone(); 259 temp.add(Calendar.MILLISECOND, 1); 260 return temp.get(Calendar.MONTH) != calendar.get(Calendar.MONTH); 261 } 262 263 /** 264 * Returns a boolean indicating if the given calendar represents the 265 * start of a month (in the calendar's time zone). Returns true, if the time is 266 * the start of the first day of the month, false otherwise. The calendar is unchanged. 267 * 268 * @param calendar the calendar to check. 269 * 270 * @return true if the calendar's time is the start of the first day of the month, 271 * false otherwise. 272 */ 273 public static boolean isStartOfWeek(Calendar calendar) { 274 Calendar temp = (Calendar) calendar.clone(); 275 temp.add(Calendar.MILLISECOND, -1); 276 return temp.get(Calendar.WEEK_OF_YEAR) != calendar.get(Calendar.WEEK_OF_YEAR); 277 } 278 279 /** 280 * Returns a boolean indicating if the given calendar represents the 281 * end of a week (in the calendar's time zone). Returns true, if the time is 282 * the end of the last day of the week, false otherwise. The calendar is unchanged. 283 * 284 * @param calendar the calendar to check. 285 * 286 * @return true if the calendar's time is the end of the last day of the week, 287 * false otherwise. 288 */ 289 public static boolean isEndOfWeek(Calendar calendar) { 290 Calendar temp = (Calendar) calendar.clone(); 291 temp.add(Calendar.MILLISECOND, 1); 292 return temp.get(Calendar.WEEK_OF_YEAR) != calendar.get(Calendar.WEEK_OF_YEAR); 293 } 294 295 /** 296 * Adjusts the calendar to the start of the current week. 297 * That is, first day of the week with all time fields cleared. 298 * @param calendar the calendar to adjust. 299 * @return the Date the calendar is set to 300 */ 301 public static void startOfWeek(Calendar calendar) { 302 calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); 303 startOfDay(calendar); 304 } 305 306 /** 307 * Adjusts the calendar to the end of the current week. 308 * That is, last day of the week with all time fields at max. 309 * @param calendar the calendar to adjust. 310 */ 311 public static void endOfWeek(Calendar calendar) { 312 startOfWeek(calendar); 313 calendar.add(Calendar.DATE, 7); 314 calendar.add(Calendar.MILLISECOND, -1); 315 } 316 317 /** 318 * Adjusts the calendar to the end of the current week. 319 * That is, last day of the week with all time fields at max. 320 * The Date of the adjusted Calendar is 321 * returned. 322 * 323 * @param calendar calendar to adjust. 324 * @param date the Date to use. 325 * @return the end of the week of the given date 326 */ 327 public static Date endOfWeek(Calendar calendar, Date date) { 328 calendar.setTime(date); 329 endOfWeek(calendar); 330 return calendar.getTime(); 331 } 332 333 /** 334 * Adjusts the calendar to the start of the current week. 335 * That is, last day of the week with all time fields at max. 336 * The Date of the adjusted Calendar is 337 * returned. 338 * 339 * @param calendar calendar to adjust. 340 * @param date the Date to use. 341 * @return the start of the week of the given date 342 */ 343 public static Date startOfWeek(Calendar calendar, Date date) { 344 calendar.setTime(date); 345 startOfWeek(calendar); 346 return calendar.getTime(); 347 } 348 349 /** 350 * Adjusts the given Calendar to the start of the decade. 351 * 352 * @param calendar the calendar to adjust. 353 */ 354 public static void startOfDecade(Calendar calendar) { 355 calendar.set(Calendar.YEAR, decade(calendar.get(Calendar.YEAR)) ); 356 startOfYear(calendar); 357 } 358 359 /** 360 * @param year 361 * @return 362 */ 363 private static int decade(int year) { 364 return (year / 10) * 10; 365 } 366 367 /** 368 * Adjusts the given Calendar to the start of the decade as defined by 369 * the given date. Returns the calendar's Date. 370 * 371 * @param calendar calendar to adjust. 372 * @param date the Date to use. 373 * @return the start of the decade of the given date 374 */ 375 public static Date startOfDecade(Calendar calendar, Date date) { 376 calendar.setTime(date); 377 startOfDecade(calendar); 378 return calendar.getTime(); 379 } 380 381 /** 382 * Returns a boolean indicating if the given calendar represents the 383 * start of a decade (in the calendar's time zone). Returns true, if the time is 384 * the start of the first day of the decade, false otherwise. The calendar is unchanged. 385 * 386 * @param calendar the calendar to check. 387 * 388 * @return true if the calendar's time is the start of the first day of the month, 389 * false otherwise. 390 */ 391 public static boolean isStartOfDecade(Calendar calendar) { 392 Calendar temp = (Calendar) calendar.clone(); 393 temp.add(Calendar.MILLISECOND, -1); 394 return decade(temp.get(Calendar.YEAR)) != decade(calendar.get(Calendar.YEAR)); 395 } 396 397 /** 398 * Adjusts the given Calendar to the start of the year. 399 * 400 * @param calendar the calendar to adjust. 401 */ 402 public static void startOfYear(Calendar calendar) { 403 calendar.set(Calendar.MONTH, Calendar.JANUARY); 404 startOfMonth(calendar); 405 } 406 407 /** 408 * Adjusts the given Calendar to the start of the year as defined by 409 * the given date. Returns the calendar's Date. 410 * 411 * @param calendar calendar to adjust. 412 * @param date the Date to use. 413 * @return the start of the year of the given date 414 */ 415 public static Date startOfYear(Calendar calendar, Date date) { 416 calendar.setTime(date); 417 startOfYear(calendar); 418 return calendar.getTime(); 419 } 420 421 /** 422 * Returns a boolean indicating if the given calendar represents the 423 * start of a year (in the calendar's time zone). Returns true, if the time is 424 * the start of the first day of the year, false otherwise. The calendar is unchanged. 425 * 426 * @param calendar the calendar to check. 427 * 428 * @return true if the calendar's time is the start of the first day of the month, 429 * false otherwise. 430 */ 431 public static boolean isStartOfYear(Calendar calendar) { 432 Calendar temp = (Calendar) calendar.clone(); 433 temp.add(Calendar.MILLISECOND, -1); 434 return temp.get(Calendar.YEAR) != calendar.get(Calendar.YEAR); 435 } 436 437 /** 438 * Adjusts the calendar to the start of the current month. 439 * That is, first day of the month with all time fields cleared. 440 * 441 * @param calendar calendar to adjust. 442 */ 443 public static void startOfMonth(Calendar calendar) { 444 calendar.set(Calendar.DAY_OF_MONTH, 1); 445 startOfDay(calendar); 446 } 447 448 /** 449 * Adjusts the calendar to the end of the current month. 450 * That is the last day of the month with all time-fields 451 * at max. 452 * 453 * @param calendar calendar to adjust. 454 */ 455 public static void endOfMonth(Calendar calendar) { 456 // start of next month 457 calendar.add(Calendar.MONTH, 1); 458 startOfMonth(calendar); 459 // one millisecond back 460 calendar.add(Calendar.MILLISECOND, -1); 461 } 462 463 /** 464 * Adjust the given calendar to the first millisecond of the given date. 465 * that is all time fields cleared. The Date of the adjusted Calendar is 466 * returned. 467 * 468 * @param calendar calendar to adjust. 469 * @param date the Date to use. 470 * @return the start of the day of the given date 471 */ 472 public static Date startOfDay(Calendar calendar, Date date) { 473 calendar.setTime(date); 474 startOfDay(calendar); 475 return calendar.getTime(); 476 } 477 478 /** 479 * Adjust the given calendar to the last millisecond of the given date. 480 * that is all time fields cleared. The Date of the adjusted Calendar is 481 * returned. 482 * 483 * @param calendar calendar to adjust. 484 * @param date the Date to use. 485 * @return the end of the day of the given date 486 */ 487 public static Date endOfDay(Calendar calendar, Date date) { 488 calendar.setTime(date); 489 endOfDay(calendar); 490 return calendar.getTime(); 491 } 492 493 /** 494 * Adjust the given calendar to the first millisecond of the current day. 495 * that is all time fields cleared. 496 * 497 * @param calendar calendar to adjust. 498 */ 499 public static void startOfDay(Calendar calendar) { 500 calendar.set(Calendar.HOUR_OF_DAY, 0); 501 calendar.set(Calendar.MILLISECOND, 0); 502 calendar.set(Calendar.SECOND, 0); 503 calendar.set(Calendar.MINUTE, 0); 504 calendar.getTimeInMillis(); 505 } 506 507 /** 508 * Adjust the given calendar to the last millisecond of the specified date. 509 * 510 * @param calendar calendar to adjust. 511 */ 512 public static void endOfDay(Calendar calendar) { 513 calendar.add(Calendar.DATE, 1); 514 startOfDay(calendar); 515 calendar.add(Calendar.MILLISECOND, -1); 516 } 517 518 /** 519 * Adjusts the given calendar to the start of the period as indicated by the 520 * given field. This delegates to startOfDay, -Week, -Month, -Year as appropriate. 521 * 522 * @param calendar 523 * @param field the period to adjust, allowed are Calendar.DAY_OF_MONTH, -.MONTH, 524 * -.WEEK and YEAR and CalendarUtils.DECADE. 525 */ 526 public static void startOf(Calendar calendar, int field) { 527 switch (field) { 528 case Calendar.DAY_OF_MONTH: 529 startOfDay(calendar); 530 break; 531 case Calendar.MONTH: 532 startOfMonth(calendar); 533 break; 534 case Calendar.WEEK_OF_YEAR: 535 startOfWeek(calendar); 536 break; 537 case Calendar.YEAR: 538 startOfYear(calendar); 539 break; 540 case DECADE: 541 startOfDecade(calendar); 542 break; 543 default: 544 throw new IllegalArgumentException("unsupported field: " + field); 545 546 } 547 } 548 549 /** 550 * Returns a boolean indicating if the calendar is set to the start of a 551 * period as defined by the 552 * given field. This delegates to startOfDay, -Week, -Month, -Year as appropriate. 553 * The calendar is unchanged. 554 * 555 * @param calendar 556 * @param field the period to adjust, allowed are Calendar.DAY_OF_MONTH, -.MONTH, 557 * -.WEEK and YEAR and CalendarUtils.DECADE. 558 * @throws IllegalArgumentException if the field is not supported. 559 */ 560 public static boolean isStartOf(Calendar calendar, int field) { 561 switch (field) { 562 case Calendar.DAY_OF_MONTH: 563 return isStartOfDay(calendar); 564 case Calendar.MONTH: 565 return isStartOfMonth(calendar); 566 case Calendar.WEEK_OF_YEAR: 567 return isStartOfWeek(calendar); 568 case Calendar.YEAR: 569 return isStartOfYear(calendar); 570 case DECADE: 571 return isStartOfDecade(calendar); 572 default: 573 throw new IllegalArgumentException("unsupported field: " + field); 574 } 575 } 576 577 /** 578 * Checks the given dates for being equal. 579 * 580 * @param current one of the dates to compare 581 * @param date the otherr of the dates to compare 582 * @return true if the two given dates both are null or both are not null and equal, 583 * false otherwise. 584 */ 585 public static boolean areEqual(Date current, Date date) { 586 if ((date == null) && (current == null)) { 587 return true; 588 } 589 if (date != null) { 590 return date.equals(current); 591 } 592 return false; 593 } 594 595 /** 596 * Returns a boolean indicating whether the given Date is the same day as 597 * the day in the calendar. Calendar and date are unchanged by the check. 598 * 599 * @param today the Calendar representing a date, must not be null. 600 * @param now the date to compare to, must not be null 601 * @return true if the calendar and date represent the same day in the 602 * given calendar. 603 */ 604 public static boolean isSameDay(Calendar today, Date now) { 605 Calendar temp = (Calendar) today.clone(); 606 startOfDay(temp); 607 Date start = temp.getTime(); 608 temp.setTime(now); 609 startOfDay(temp); 610 return start.equals(temp.getTime()); 611 } 612 613 /** 614 * Returns a boolean indicating whether the given Date is in the same period as 615 * the Date in the calendar, as defined by the calendar field. 616 * Calendar and date are unchanged by the check. 617 * 618 * @param today the Calendar representing a date, must not be null. 619 * @param now the date to compare to, must not be null 620 * @return true if the calendar and date represent the same day in the 621 * given calendar. 622 */ 623 public static boolean isSame(Calendar today, Date now, int field) { 624 Calendar temp = (Calendar) today.clone(); 625 startOf(temp, field); 626 Date start = temp.getTime(); 627 temp.setTime(now); 628 startOf(temp, field); 629 return start.equals(temp.getTime()); 630 } 631 632 /** 633 * Returns a boolean to indicate whether the given calendar is flushed. <p> 634 * 635 * The only way to guarantee a flushed state is to let client code call 636 * getTime or getTimeInMillis. See 637 * 638 * <a href=http://forums.java.net/jive/thread.jspa?threadID=74472&tstart=0>Despairing 639 * in Calendar</a> 640 * <p> 641 * Note: this if for testing only and not entirely safe! 642 * 643 * @param calendar 644 * @return 645 */ 646 public static boolean isFlushed(Calendar calendar) { 647 return !calendar.toString().contains("time=?"); 648 } 649}