001    package org.rakeshv.utils;
002    
003    import java.util.GregorianCalendar;
004    import java.util.Locale;
005    import java.util.TimeZone;
006    
007    /**
008     * A wrapper around <code>java.util.GregorianCalendar</code>.  Setting 
009     * the <code>TimeZone</code> of this <code>Calendar</code> will
010     * automatically adjust the underlying <code>Date</code> by
011     * offsetting from the <code>existing TimeZone</code>.
012     *
013     * <p>Java always represents the time in UTC.  This can sometimes be
014     * incovenient especially when dealing with RDBMS systems, where
015     * fetching a date/time/timestamp value will return a value with the
016     * millisecond value that represents the value in current TimeZone
017     * as opposed to UTC.  This class can be used to conveniently correct
018     * the internal time represented.</p>
019     *
020     * <p>The following code snippet shows a way of using this class:</p>
021     * <pre> 
022     *    import java.util.Calendar;
023     *    import java.util.TimeZone;
024     *
025     *        Calendar local = org.rakeshv.utils.Calendar.getInstance();
026     *        System.out.println( "local " + local.getTime() ); 
027     * 
028     *        Calendar utc = org.rakeshv.utils.Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
029     *        System.out.println( "utc " + utc.getTime() );
030     *
031     *        Calendar pst = org.rakeshv.utils.Calendar.getInstance( TimeZone.getTimeZone( "America/Los_Angeles" ) );
032     *        System.out.println( "pst " + pst.getTime() );
033     *        pst.setTimeZone( TimeZone.getDefault() );
034     *        System.out.println( "pst in local " + pst.getTime() );
035     * </pre>
036     *
037     * <p>Copyright 2004-2006 Rakesh Vidyadharan</p>
038     * @author Rakesh Vidyadharan 2004 August 18
039     * @version $Id: Calendar.java,v 1.6 2006/03/11 04:15:14 rakesh Exp $
040     */
041    public class Calendar extends GregorianCalendar
042    {
043      /**
044       * Constructs a Calendar with the default time zone and locale.
045       * Just invokes the super class constructor.
046       */
047      public Calendar()
048      {
049        super();
050      }
051    
052      /**
053       * Constructs a Calendar based on the current time in the given time 
054       * zone with the default locale.  The <code>time</code> for the
055       * calendar is set to the equivalent for the specified time zone
056       * based upon the system time in the system time zone.
057       *
058       * @see #setTimeZone( TimeZone )
059       * @param timeZone - The <code>TimeZone</code> to use.
060       */
061      public Calendar( TimeZone timeZone )
062      {
063        super();
064        setTimeZone( timeZone );
065      }
066    
067      /**
068       * Constructs a Calendar based on the current time in the default 
069       * time zone with the given locale.
070       *
071       * @param locale - The <code>Locale</code> to use.
072       */
073      public Calendar( Locale locale )
074      {
075        super( locale );
076      }
077    
078      /**
079       * Constructs a calendar with the specified time zone and locale.  
080       * The <code>time</code> for the calendar is set to the equivalent 
081       * for the specified time zone based upon the system time in the 
082       * system time zone.
083       *
084       * @see #setTimeZone( TimeZone )
085       * @param timeZone - The <code>TimeZone</code> to use.
086       * @param locale - The <code>Locale</code> to use.
087       */
088      public Calendar( TimeZone timeZone, Locale locale )
089      {
090        super( locale );
091        setTimeZone( timeZone );
092      }
093    
094      /**
095       * Gets a calendar using the default time zone and locale. The 
096       * Calendar returned is based on the current time in the default time 
097       * zone with the default locale.
098       *
099       * @see #Calendar()
100       * @return java.util.Calendar - A new instance of {@link Calendar}.
101       */
102      public static java.util.Calendar getInstance()
103      {
104        return new Calendar();
105      }
106    
107      /**
108       * Gets a calendar using the specified time zone and default locale. 
109       * The Calendar returned is based on the current time in the given 
110       * time zone corresponding to the system time in the system time 
111       * zone with the default locale.
112       *
113       * @see #Calendar( TimeZone )
114       * @param timeZone - The <code>TimeZone</code> to use.
115       * @return java.util.Calendar - A new instance of {@link Calendar}.
116       */
117      public static java.util.Calendar getInstance( TimeZone timeZone )
118      {
119        return new Calendar( timeZone );
120      }
121    
122      /**
123       * Gets a calendar using the default time zone and specified locale. 
124       * The Calendar returned is based on the current time in the default 
125       * time zone with the given locale.
126       *
127       * @see #Calendar( Locale )
128       * @param locale - The <code>Locale</code> to use.
129       * @return java.util.Calendar - A new instance of {@link Calendar}.
130       */
131      public static java.util.Calendar getInstance( Locale locale )
132      {
133        return new Calendar( locale );
134      }
135    
136      /**
137       * Gets a calendar with the specified time zone and locale. The 
138       * Calendar returned is based on the current time in the given time 
139       * zone corresponding to the system time in the sytem time zone
140       * with the given locale.
141       *
142       * @see #Calendar( TimeZone, Locale )
143       * @param timeZone - The <code>TimeZone</code> to use.
144       * @param locale - The <code>Locale</code> to use.
145       * @return java.util.Calendar - A new instance of {@link Calendar}.
146       */
147      public static java.util.Calendar getInstance( TimeZone timeZone, 
148          Locale locale )
149      {
150        return new Calendar( timeZone, locale );
151      }
152    
153      /**
154       * Sets the time zone with the given time zone value.  The <code>
155       * time</code> value is modified by the appropriate 
156       * <code>offset</code> based upon the current time zone.
157       */
158      @Override
159      public void setTimeZone( TimeZone timeZone )
160      {
161        int currentOffset = 
162          get( Calendar.ZONE_OFFSET ) + get( Calendar.DST_OFFSET );
163        super.setTimeZone( timeZone );
164        int newOffset = 
165          get( Calendar.ZONE_OFFSET ) + get( Calendar.DST_OFFSET );
166        int correction = currentOffset - newOffset;
167        setTimeInMillis( getTimeInMillis() - correction );
168      } 
169    
170      /**
171       * Sets the given calendar field to the given value. Over-ridden
172       * to force <code>HOUR</code> and <code>HOUR_OF_DAY</code> to
173       * apply proper correction when the <code>timeZone</code> is not
174       * the local timeZone.
175       *
176       * @param field The given calendar field.
177       * @param value The value to be set for the given calendar field.
178       * @throws ArrayIndexOutOfBoundsException if the specified field is 
179       *   out of range (<code>field &lt; 0 || field &gt;= FIELD_COUNT</code>).
180       *   In non-lenient mode.
181       */
182      @Override
183      public void set( int field, int value )
184      {
185        switch ( field )
186        {
187          case Calendar.HOUR:
188          case Calendar.HOUR_OF_DAY:
189            TimeZone timeZone = getTimeZone();
190            int currentOffset = 
191              get( Calendar.ZONE_OFFSET ) + get( Calendar.DST_OFFSET );
192            setTimeZone( TimeZone.getDefault() );
193            int localOffset = 
194              get( Calendar.ZONE_OFFSET ) + get( Calendar.DST_OFFSET );
195            int correction = 
196              ( currentOffset - localOffset ) / ( 60 * 60 * 1000 );
197            value += correction;
198            setTimeZone( timeZone );
199            super.set( field, value );
200            break;
201          default:
202            super.set( field, value );
203            break;
204        }
205      }
206    
207      /**
208       * A contant used to indicate that the <code>MINUTE</code> of this
209       * calendar is to be rounded up to the nearest multiple of 5.  Used
210       * by the {@link #roundMinutes} method.
211       *
212       * {@value}
213       */
214      public static final int ROUND_UP = 1;
215    
216      /**
217       * A contant used to indicate that the <code>MINUTE</code> of this
218       * calendar is to be rounded down to the nearest multiple of 5.  Used
219       * by the {@link #roundMinutes} method.
220       *
221       * {@value}
222       */
223      public static final int ROUND_DOWN = 2;
224    
225      /**
226       * A contant used to indicate that the <code>MINUTE</code> of this
227       * calendar is to be rounded automatically using normal rounding
228       * rules to the nearest multiple of 5.  Used by the 
229       * {@link #roundMinutes} method.
230       *
231       * {@value}
232       */
233      public static final int ROUND_AUTO = 3;
234      
235      /**
236       * Round the <code>MINUTE</code> part of this calendar to the nearest
237       * 5 minute mark.  This is useful for creating drop-down menus in 
238       * user interface components.
239       *
240       * @param minInterval The interval to use for rounding.  The value
241       *   must be at least 5 (it will be corrected to 5 otherwise), and
242       *   should be a multiple of 5 (will be corrected to a multiple of
243       *   5 otherwise).
244       * @param rounding A constant used to indicate the direction in which
245       *   the rounding is to be performed.  Specify one of {@link
246       *   #ROUND_UP}, {@link #ROUND_DOWN}, or {@link #ROUND_AUTO}.
247       * @return Calendar Returns a reference to this object.
248       */
249      public Calendar roundMinutes( int minInterval, int rounding )
250            {
251        if ( minInterval < 5 )
252        {
253                            minInterval = 5;
254        }
255    
256        // Round only in 5 minutes increments
257                    int fixedInterval = ( minInterval / 5 ) * 5;
258    
259                    if ( fixedInterval > 30 && fixedInterval != 60 )
260        {
261                            fixedInterval = 30;
262        }
263    
264                    int minute = get( Calendar.MINUTE );
265                    set( Calendar.SECOND, 0 );
266                    set( Calendar.MINUTE, 0 );
267                    double tmpMinMin = (double) ( minute / fixedInterval );
268    
269        switch ( rounding )
270        {
271          case ROUND_UP:
272            minute = (int) ( Math.ceil( tmpMinMin ) * fixedInterval );
273            break;
274          case ROUND_DOWN:
275            minute= (int) ( Math.floor( tmpMinMin ) * fixedInterval );
276            break;
277          case ROUND_AUTO:
278          default:
279            minute= (int) ( Math.round( tmpMinMin ) * fixedInterval );
280            break;
281        }
282    
283                    add( Calendar.MINUTE, minute );
284    
285                    return this;
286            }
287    }