001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.jdbc.util;
017
018import java.sql.Date;
019import java.sql.Time;
020import java.sql.Timestamp;
021import java.text.DateFormat;
022import java.text.SimpleDateFormat;
023import java.util.Calendar;
024import java.util.GregorianCalendar;
025import java.util.TimeZone;
026
027/**
028 * Utility methods for SQL Timestamps, Time, and Dates with time zones as UTC This is intended to take incoming Strings or Dates
029 * that have accurate Calendar fields and give the UTC time by interpretting those fields in the target time zone. Use of the
030 * Calendar object passed in will not be thread safe, but it will not alter the contents of the Calendar. Note that normalization
031 * occurs only for the transition from one type to another.
032 */
033public class TimestampWithTimezone {
034
035    public static DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$
036    public static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
037    public static DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss a"); //$NON-NLS-1$
038
039    private static ThreadLocal<Calendar> CALENDAR = new ThreadLocal<Calendar>() {
040        @Override
041        protected Calendar initialValue() {
042            return Calendar.getInstance();
043        }
044    };
045
046    public static Calendar getCalendar() {
047        return CALENDAR.get();
048    }
049
050    public static void resetCalendar( TimeZone tz ) {
051        TimeZone.setDefault(tz);
052        Calendar cal = Calendar.getInstance();
053        cal.setTimeZone(tz);
054        CALENDAR.set(cal);
055    }
056
057    public static Timestamp createTimestamp( Calendar initial,
058                                             Calendar target ) {
059        if (target == null) {
060            target = getCalendar();
061        }
062
063        long time = target.getTimeInMillis();
064
065        Calendar new_target = adjustCalendarForTimeStamp(initial, target);
066
067        Timestamp tsInTz = createTimestamp(new_target);
068
069        target.setTimeInMillis(time);
070        return tsInTz;
071    }
072
073    public static Time createTime( Calendar initial,
074                                   Calendar target ) {
075        if (target == null) {
076            // if target is null, then obtain current calendar
077            target = getCalendar();
078        }
079
080        long time = target.getTimeInMillis();
081
082        adjustCalendarForTime(initial, target);
083
084        target.set(Calendar.MILLISECOND, 0);
085
086        Time result = createTime(target);
087
088        target.setTimeInMillis(time);
089
090        return result;
091    }
092
093    public static Date createDate( Calendar initial,
094                                   Calendar target ) {
095        if (target == null) {
096            return createDate(initial);
097        }
098
099        long time = target.getTimeInMillis();
100
101        target = adjustCalendarForDate(initial, target);
102
103        Date result = normalizeDate(target, true);
104
105        target.setTimeInMillis(time);
106
107        return result;
108    }
109
110    /**
111     * Creates normalized SQL Time Object based on the target Calendar.
112     * 
113     * @param target Calendar
114     * @return Time
115     */
116    public static Time createTime( Calendar target ) {
117        return new Time(target.getTimeInMillis());
118    }
119
120    /**
121     * Creates normalized SQL Date Object based on the target Calendar
122     * 
123     * @param target Calendar
124     * @return Date
125     */
126    public static Date createDate( Calendar target ) {
127        return new java.sql.Date(target.getTime().getTime());
128    }
129
130    /**
131     * Creates normalized SQL Timestamp Object based on the target Calendar
132     * 
133     * @param target Calendar
134     * @return Timestamp
135     */
136    public static Timestamp createTimestamp( Calendar target ) {
137        return new Timestamp(target.getTime().getTime());
138    }
139
140    private static Date normalizeDate( Calendar target,
141                                       boolean isDate ) {
142        if (isDate) {
143            target.set(Calendar.HOUR_OF_DAY, 0);
144            target.set(Calendar.MINUTE, 0);
145            target.set(Calendar.SECOND, 0);
146            target.set(Calendar.MILLISECOND, 0);
147        }
148        return createDate(target);
149    }
150
151    private static void adjustCalendarForTime( Calendar initial,
152                                               Calendar target ) {
153        assert initial != null;
154
155        if (initial.getTimeZone().hasSameRules(target.getTimeZone())) {
156            target.setTime(initial.getTime());
157            return;
158        }
159
160        target.clear();
161        for (int i = 0; i <= Calendar.MILLISECOND; i++) {
162            target.set(i, initial.get(i));
163        }
164    }
165
166    private static Calendar adjustCalendarForDate( Calendar initial,
167                                                   Calendar target ) {
168        assert initial != null;
169
170        if (initial.getTimeZone().hasSameRules(target.getTimeZone())) {
171            target.setTime(initial.getTime());
172            return target;
173        }
174
175        Calendar ntarget = new GregorianCalendar(target.getTimeZone());
176        ntarget.setTimeInMillis(initial.getTimeInMillis());
177
178        return ntarget;
179    }
180
181    private static Calendar adjustCalendarForTimeStamp( Calendar initial,
182                                                        Calendar target ) {
183        assert initial != null;
184
185        if (initial.getTimeZone().hasSameRules(target.getTimeZone())) {
186            target.setTime(initial.getTime());
187            return target;
188        }
189
190        TimeZone targetTimeZone = target.getTimeZone();
191        Calendar ret = new GregorianCalendar(targetTimeZone);
192        ret.setTimeInMillis(initial.getTimeInMillis() + targetTimeZone.getOffset(initial.getTimeInMillis())
193                            - initial.getTimeZone().getOffset(initial.getTimeInMillis()));
194        ret.getTime();
195        return ret;
196    }
197}