001    /*
002     *  Copyright 2001-2013 Stephen Colebourne
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     */
016    package org.joda.time;
017    
018    import org.joda.time.format.DateTimeFormat;
019    
020    /**
021     * Exception thrown when attempting to create an instant or date-time that cannot exist.
022     * <p>
023     * Classes like {@code DateTime} only store valid date-times.
024     * One of the cases where validity is important is handling daylight savings time (DST).
025     * In many places DST is used, where the local clock moves forward by an hour in spring and back by an hour in autumn/fall.
026     * This means that in spring, there is a "gap" where a local time does not exist.
027     * <p>
028     * This exception refers to this gap, and it means that your application tried to create
029     * a date-time inside the gap - a time that did not exist.
030     * Since Joda-Time objects must be valid, this is not allowed.
031     * <p>
032     * Possible solutions may be as follows:<br />
033     * Use <code>LocalDateTime</code>, as all local date-times are valid.<br />
034     * When converting a <code>LocalDate</code> to a <code>DateTime</code>, then use <code>toDateTimeAsStartOfDay()</code>
035     * as this handles and manages any gaps.<br />
036     * When parsing, use <code>parseLocalDateTime()</code> if the string being parsed has no time-zone.
037     *
038     * @author Stephen Colebourne
039     * @since 2.2
040     */
041    public class IllegalInstantException extends IllegalArgumentException {
042        
043        /** Serialization lock. */
044        private static final long serialVersionUID = 2858712538216L;
045    
046    
047        /**
048         * Constructor.
049         * 
050         * @param message  the message
051         */
052        public IllegalInstantException(String message) {
053            super(message);
054        }
055    
056        /**
057         * Constructor.
058         * 
059         * @param instantLocal  the local instant
060         * @param zoneId  the time-zone ID, may be null
061         */
062        public IllegalInstantException(long instantLocal, String zoneId) {
063            super(createMessage(instantLocal, zoneId));
064        }
065    
066        private static String createMessage(long instantLocal, String zoneId) {
067            String localDateTime = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS").print(new Instant(instantLocal));
068            String zone = (zoneId != null ? " (" + zoneId + ")" : "");
069            return "Illegal instant due to time zone offset transition (daylight savings time 'gap'): " + localDateTime + zone;
070        }
071    
072        //-----------------------------------------------------------------------
073        /**
074         * Checks if the exception is, or has a cause, of {@code IllegalInstantException}.
075         * 
076         * @param ex  the exception to check
077         * @return true if an {@code IllegalInstantException}
078         */
079        public static boolean isIllegalInstant(Throwable ex) {
080            if (ex instanceof IllegalInstantException) {
081                return true;
082            }
083            while (ex.getCause() != null && ex.getCause() != ex) {
084                return isIllegalInstant(ex.getCause());
085            }
086            return false;
087        }
088    
089    }