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.DateTimeZone; 026 import org.joda.time.field.SkipDateTimeField; 027 028 /** 029 * Implements the Ethiopic calendar system, which defines every fourth year as 030 * leap, much like the Julian calendar. The year is broken down into 12 months, 031 * each 30 days in length. An extra period at the end of the year is either 5 032 * or 6 days in length. In this implementation, it is considered a 13th month. 033 * <p> 034 * Year 1 in the Ethiopic calendar began on August 29, 8 CE (Julian), thus 035 * Ethiopic years do not begin at the same time as Julian years. This chronology 036 * is not proleptic, as it does not allow dates before the first Ethiopic year. 037 * <p> 038 * This implementation defines a day as midnight to midnight exactly as per 039 * the ISO chronology. Some references indicate that a coptic day starts at 040 * sunset on the previous ISO day, but this has not been confirmed and is not 041 * implemented. 042 * <p> 043 * EthiopicChronology is thread-safe and immutable. 044 * 045 * @see <a href="http://en.wikipedia.org/wiki/Ethiopian_calendar">Wikipedia</a> 046 * 047 * @author Brian S O'Neill 048 * @author Stephen Colebourne 049 * @since 1.2 050 */ 051 public final class EthiopicChronology extends BasicFixedMonthChronology { 052 053 /** Serialization lock */ 054 private static final long serialVersionUID = -5972804258688333942L; 055 056 /** 057 * Constant value for 'Ethiopean Era', equivalent 058 * to the value returned for AD/CE. 059 */ 060 public static final int EE = DateTimeConstants.CE; 061 062 /** A singleton era field. */ 063 private static final DateTimeField ERA_FIELD = new BasicSingleEraDateTimeField("EE"); 064 065 /** The lowest year that can be fully supported. */ 066 private static final int MIN_YEAR = -292269337; 067 068 /** The highest year that can be fully supported. */ 069 private static final int MAX_YEAR = 292272984; 070 071 /** Cache of zone to chronology arrays */ 072 private static final Map<DateTimeZone, EthiopicChronology[]> cCache = new HashMap<DateTimeZone, EthiopicChronology[]>(); 073 074 /** Singleton instance of a UTC EthiopicChronology */ 075 private static final EthiopicChronology INSTANCE_UTC; 076 static { 077 // init after static fields 078 INSTANCE_UTC = getInstance(DateTimeZone.UTC); 079 } 080 081 //----------------------------------------------------------------------- 082 /** 083 * Gets an instance of the EthiopicChronology. 084 * The time zone of the returned instance is UTC. 085 * 086 * @return a singleton UTC instance of the chronology 087 */ 088 public static EthiopicChronology getInstanceUTC() { 089 return INSTANCE_UTC; 090 } 091 092 /** 093 * Gets an instance of the EthiopicChronology in the default time zone. 094 * 095 * @return a chronology in the default time zone 096 */ 097 public static EthiopicChronology getInstance() { 098 return getInstance(DateTimeZone.getDefault(), 4); 099 } 100 101 /** 102 * Gets an instance of the EthiopicChronology in the given time zone. 103 * 104 * @param zone the time zone to get the chronology in, null is default 105 * @return a chronology in the specified time zone 106 */ 107 public static EthiopicChronology getInstance(DateTimeZone zone) { 108 return getInstance(zone, 4); 109 } 110 111 /** 112 * Gets an instance of the EthiopicChronology in the given time zone. 113 * 114 * @param zone the time zone to get the chronology in, null is default 115 * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4 116 * @return a chronology in the specified time zone 117 */ 118 public static EthiopicChronology getInstance(DateTimeZone zone, int minDaysInFirstWeek) { 119 if (zone == null) { 120 zone = DateTimeZone.getDefault(); 121 } 122 EthiopicChronology chrono; 123 synchronized (cCache) { 124 EthiopicChronology[] chronos = cCache.get(zone); 125 if (chronos == null) { 126 chronos = new EthiopicChronology[7]; 127 cCache.put(zone, chronos); 128 } 129 try { 130 chrono = chronos[minDaysInFirstWeek - 1]; 131 } catch (ArrayIndexOutOfBoundsException e) { 132 throw new IllegalArgumentException 133 ("Invalid min days in first week: " + minDaysInFirstWeek); 134 } 135 if (chrono == null) { 136 if (zone == DateTimeZone.UTC) { 137 // First create without a lower limit. 138 chrono = new EthiopicChronology(null, null, minDaysInFirstWeek); 139 // Impose lower limit and make another EthiopicChronology. 140 DateTime lowerLimit = new DateTime(1, 1, 1, 0, 0, 0, 0, chrono); 141 chrono = new EthiopicChronology 142 (LimitChronology.getInstance(chrono, lowerLimit, null), 143 null, minDaysInFirstWeek); 144 } else { 145 chrono = getInstance(DateTimeZone.UTC, minDaysInFirstWeek); 146 chrono = new EthiopicChronology 147 (ZonedChronology.getInstance(chrono, zone), null, minDaysInFirstWeek); 148 } 149 chronos[minDaysInFirstWeek - 1] = chrono; 150 } 151 } 152 return chrono; 153 } 154 155 // Constructors and instance variables 156 //----------------------------------------------------------------------- 157 /** 158 * Restricted constructor. 159 */ 160 EthiopicChronology(Chronology base, Object param, int minDaysInFirstWeek) { 161 super(base, param, minDaysInFirstWeek); 162 } 163 164 /** 165 * Serialization singleton. 166 */ 167 private Object readResolve() { 168 Chronology base = getBase(); 169 return base == null ? 170 getInstance(DateTimeZone.UTC, getMinimumDaysInFirstWeek()) : 171 getInstance(base.getZone(), getMinimumDaysInFirstWeek()); 172 } 173 174 // Conversion 175 //----------------------------------------------------------------------- 176 /** 177 * Gets the Chronology in the UTC time zone. 178 * 179 * @return the chronology in UTC 180 */ 181 public Chronology withUTC() { 182 return INSTANCE_UTC; 183 } 184 185 /** 186 * Gets the Chronology in a specific time zone. 187 * 188 * @param zone the zone to get the chronology in, null is default 189 * @return the chronology 190 */ 191 public Chronology withZone(DateTimeZone zone) { 192 if (zone == null) { 193 zone = DateTimeZone.getDefault(); 194 } 195 if (zone == getZone()) { 196 return this; 197 } 198 return getInstance(zone); 199 } 200 201 //----------------------------------------------------------------------- 202 long calculateFirstDayOfYearMillis(int year) { 203 // Java epoch is 1970-01-01 Gregorian which is 1962-04-23 Ethiopic. 204 // Calculate relative to the nearest leap year and account for the 205 // difference later. 206 207 int relativeYear = year - 1963; 208 int leapYears; 209 if (relativeYear <= 0) { 210 // Add 3 before shifting right since /4 and >>2 behave differently 211 // on negative numbers. 212 leapYears = (relativeYear + 3) >> 2; 213 } else { 214 leapYears = relativeYear >> 2; 215 // For post 1963 an adjustment is needed as jan1st is before leap day 216 if (!isLeapYear(year)) { 217 leapYears++; 218 } 219 } 220 221 long millis = (relativeYear * 365L + leapYears) 222 * (long)DateTimeConstants.MILLIS_PER_DAY; 223 224 // Adjust to account for difference between 1963-01-01 and 1962-04-23. 225 226 return millis + (365L - 112) * DateTimeConstants.MILLIS_PER_DAY; 227 } 228 229 //----------------------------------------------------------------------- 230 int getMinYear() { 231 return MIN_YEAR; 232 } 233 234 //----------------------------------------------------------------------- 235 int getMaxYear() { 236 return MAX_YEAR; 237 } 238 239 //----------------------------------------------------------------------- 240 long getApproxMillisAtEpochDividedByTwo() { 241 return (1962L * MILLIS_PER_YEAR + 112L * DateTimeConstants.MILLIS_PER_DAY) / 2; 242 } 243 244 //----------------------------------------------------------------------- 245 protected void assemble(Fields fields) { 246 if (getBase() == null) { 247 super.assemble(fields); 248 249 // Ethiopic, like Julian, has no year zero. 250 fields.year = new SkipDateTimeField(this, fields.year); 251 fields.weekyear = new SkipDateTimeField(this, fields.weekyear); 252 253 fields.era = ERA_FIELD; 254 fields.monthOfYear = new BasicMonthOfYearDateTimeField(this, 13); 255 fields.months = fields.monthOfYear.getDurationField(); 256 } 257 } 258 259 }