001    /*
002     *  Copyright 2001-2009 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.chrono;
017    
018    import java.util.HashMap;
019    import java.util.Map;
020    
021    import org.joda.time.Chronology;
022    import org.joda.time.DateTime;
023    import org.joda.time.DateTimeConstants;
024    import org.joda.time.DateTimeField;
025    import org.joda.time.DateTimeFieldType;
026    import org.joda.time.DateTimeZone;
027    import org.joda.time.field.DelegatedDateTimeField;
028    import org.joda.time.field.DividedDateTimeField;
029    import org.joda.time.field.OffsetDateTimeField;
030    import org.joda.time.field.RemainderDateTimeField;
031    import org.joda.time.field.SkipUndoDateTimeField;
032    
033    /**
034     * A chronology that matches the BuddhistCalendar class supplied by Sun.
035     * <p>
036     * The chronology is identical to the Gregorian/Julian, except that the
037     * year is offset by +543 and the era is named 'BE' for Buddhist Era.
038     * <p>
039     * This class was intended by Sun to model the calendar used in Thailand.
040     * However, the actual rules for Thailand are much more involved than
041     * this class covers. (This class is accurate after 1941-01-01 ISO).
042     * <p>
043     * This chronlogy is being retained for those who want a same effect
044     * replacement for the Sun class. It is hoped that community support will
045     * enable a more accurate chronology for Thailand, to be developed.
046     * <p>
047     * BuddhistChronology is thread-safe and immutable.
048     *
049     * @author Stephen Colebourne
050     * @author Brian S O'Neill
051     * @since 1.0
052     */
053    public final class BuddhistChronology extends AssembledChronology {
054        
055        /** Serialization lock */
056        private static final long serialVersionUID = -3474595157769370126L;
057    
058        /**
059         * Constant value for 'Buddhist Era', equivalent to the value returned
060         * for AD/CE. Note that this differs from the constant in BuddhistCalendar.
061         */
062        public static final int BE = DateTimeConstants.CE;
063    
064        /** A singleton era field. */
065        private static final DateTimeField ERA_FIELD = new BasicSingleEraDateTimeField("BE");
066    
067        /** Number of years difference in calendars. */
068        private static final int BUDDHIST_OFFSET = 543;
069    
070        /** Cache of zone to chronology */
071        private static final Map<DateTimeZone, BuddhistChronology> cCache = new HashMap<DateTimeZone, BuddhistChronology>();
072    
073        /** UTC instance of the chronology */
074        private static final BuddhistChronology INSTANCE_UTC = getInstance(DateTimeZone.UTC);
075    
076        /**
077         * Standard instance of a Buddhist Chronology, that matches
078         * Sun's BuddhistCalendar class. This means that it follows the
079         * GregorianJulian calendar rules with a cutover date.
080         * <p>
081         * The time zone of the returned instance is UTC.
082         */
083        public static BuddhistChronology getInstanceUTC() {
084            return INSTANCE_UTC;
085        }
086    
087        /**
088         * Standard instance of a Buddhist Chronology, that matches
089         * Sun's BuddhistCalendar class. This means that it follows the
090         * GregorianJulian calendar rules with a cutover date.
091         */
092        public static BuddhistChronology getInstance() {
093            return getInstance(DateTimeZone.getDefault());
094        }
095    
096        /**
097         * Standard instance of a Buddhist Chronology, that matches
098         * Sun's BuddhistCalendar class. This means that it follows the
099         * GregorianJulian calendar rules with a cutover date.
100         *
101         * @param zone  the time zone to use, null is default
102         */
103        public static synchronized BuddhistChronology getInstance(DateTimeZone zone) {
104            if (zone == null) {
105                zone = DateTimeZone.getDefault();
106            }
107            BuddhistChronology chrono = cCache.get(zone);
108            if (chrono == null) {
109                // First create without a lower limit.
110                chrono = new BuddhistChronology(GJChronology.getInstance(zone, null), null);
111                // Impose lower limit and make another BuddhistChronology.
112                DateTime lowerLimit = new DateTime(1, 1, 1, 0, 0, 0, 0, chrono);
113                chrono = new BuddhistChronology(LimitChronology.getInstance(chrono, lowerLimit, null), "");
114                cCache.put(zone, chrono);
115            }
116            return chrono;
117        }
118    
119        // Constructors and instance variables
120        //-----------------------------------------------------------------------
121        
122        /**
123         * Restricted constructor.
124         *
125         * @param param if non-null, then don't change the field set
126         */
127        private BuddhistChronology(Chronology base, Object param) {
128            super(base, param);
129        }
130    
131        /**
132         * Serialization singleton
133         */
134        private Object readResolve() {
135            Chronology base = getBase();
136            return base == null ? getInstanceUTC() : getInstance(base.getZone());
137        }
138    
139        // Conversion
140        //-----------------------------------------------------------------------
141        /**
142         * Gets the Chronology in the UTC time zone.
143         * 
144         * @return the chronology in UTC
145         */
146        public Chronology withUTC() {
147            return INSTANCE_UTC;
148        }
149    
150        /**
151         * Gets the Chronology in a specific time zone.
152         * 
153         * @param zone  the zone to get the chronology in, null is default
154         * @return the chronology
155         */
156        public Chronology withZone(DateTimeZone zone) {
157            if (zone == null) {
158                zone = DateTimeZone.getDefault();
159            }
160            if (zone == getZone()) {
161                return this;
162            }
163            return getInstance(zone);
164        }
165    
166        /**
167         * Checks if this chronology instance equals another.
168         * 
169         * @param obj  the object to compare to
170         * @return true if equal
171         * @since 1.6
172         */
173        public boolean equals(Object obj) {
174            return super.equals(obj);
175        }
176    
177        /**
178         * A suitable hash code for the chronology.
179         * 
180         * @return the hash code
181         * @since 1.6
182         */
183        public int hashCode() {
184            return "Buddhist".hashCode() * 11 + getZone().hashCode();
185        }
186    
187        // Output
188        //-----------------------------------------------------------------------
189        /**
190         * Gets a debugging toString.
191         * 
192         * @return a debugging string
193         */
194        public String toString() {
195            String str = "BuddhistChronology";
196            DateTimeZone zone = getZone();
197            if (zone != null) {
198                str = str + '[' + zone.getID() + ']';
199            }
200            return str;
201        }
202    
203        protected void assemble(Fields fields) {
204            if (getParam() == null) {
205                // julian chrono removed zero, but we need to put it back
206                DateTimeField field = fields.year;
207                fields.year = new OffsetDateTimeField(
208                        new SkipUndoDateTimeField(this, field), BUDDHIST_OFFSET);
209                
210                // one era, so yearOfEra is the same
211                field = fields.yearOfEra;
212                fields.yearOfEra = new DelegatedDateTimeField(
213                    fields.year, DateTimeFieldType.yearOfEra());
214                
215                // julian chrono removed zero, but we need to put it back
216                field = fields.weekyear;
217                fields.weekyear = new OffsetDateTimeField(
218                        new SkipUndoDateTimeField(this, field), BUDDHIST_OFFSET);
219                
220                field = new OffsetDateTimeField(fields.yearOfEra, 99);
221                fields.centuryOfEra = new DividedDateTimeField(
222                    field, DateTimeFieldType.centuryOfEra(), 100);
223                
224                field = new RemainderDateTimeField(
225                    (DividedDateTimeField) fields.centuryOfEra);
226                fields.yearOfCentury = new OffsetDateTimeField(
227                    field, DateTimeFieldType.yearOfCentury(), 1);
228                
229                field = new RemainderDateTimeField(
230                    fields.weekyear, DateTimeFieldType.weekyearOfCentury(), 100);
231                fields.weekyearOfCentury = new OffsetDateTimeField(
232                    field, DateTimeFieldType.weekyearOfCentury(), 1);
233                
234                fields.era = ERA_FIELD;
235            }
236        }
237       
238    }