1 /*
   2  * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.util.calendar;
  27 
  28 import java.io.IOException;
  29 import java.util.ArrayList;
  30 import java.util.List;
  31 import java.util.Properties;
  32 import java.util.StringTokenizer;
  33 import java.util.TimeZone;
  34 
  35 /**
  36  *
  37  * @author Masayoshi Okutsu
  38  * @since 1.6
  39  */
  40 
  41 public class LocalGregorianCalendar extends BaseCalendar {
  42     private String name;
  43     private Era[] eras;
  44 
  45     public static class Date extends BaseCalendar.Date {
  46 
  47         protected Date() {
  48             super();
  49         }
  50 
  51         protected Date(TimeZone zone) {
  52             super(zone);
  53         }
  54 
  55         private int gregorianYear = FIELD_UNDEFINED;
  56 
  57         @Override
  58         public Date setEra(Era era) {
  59             if (getEra() != era) {
  60                 super.setEra(era);
  61                 gregorianYear = FIELD_UNDEFINED;
  62             }
  63             return this;
  64         }
  65 
  66         @Override
  67         public Date addYear(int localYear) {
  68             super.addYear(localYear);
  69             gregorianYear += localYear;
  70             return this;
  71         }
  72 
  73         @Override
  74         public Date setYear(int localYear) {
  75             if (getYear() != localYear) {
  76                 super.setYear(localYear);
  77                 gregorianYear = FIELD_UNDEFINED;
  78             }
  79             return this;
  80         }
  81 
  82         @Override
  83         public int getNormalizedYear() {
  84             return gregorianYear;
  85         }
  86 
  87         @Override
  88         public void setNormalizedYear(int normalizedYear) {
  89             this.gregorianYear = normalizedYear;
  90         }
  91 
  92         void setLocalEra(Era era) {
  93             super.setEra(era);
  94         }
  95 
  96         void setLocalYear(int year) {
  97             super.setYear(year);
  98         }
  99 
 100         @Override
 101         public String toString() {
 102             String time = super.toString();
 103             time = time.substring(time.indexOf('T'));
 104             StringBuffer sb = new StringBuffer();
 105             Era era = getEra();
 106             if (era != null) {
 107                 String abbr = era.getAbbreviation();
 108                 if (abbr != null) {
 109                     sb.append(abbr);
 110                 }
 111             }
 112             sb.append(getYear()).append('.');
 113             CalendarUtils.sprintf0d(sb, getMonth(), 2).append('.');
 114             CalendarUtils.sprintf0d(sb, getDayOfMonth(), 2);
 115             sb.append(time);
 116             return sb.toString();
 117         }
 118     }
 119 
 120     static LocalGregorianCalendar getLocalGregorianCalendar(String name) {
 121         Properties calendarProps;
 122         try {
 123             calendarProps = CalendarSystem.getCalendarProperties();
 124         } catch (IOException | IllegalArgumentException e) {
 125             throw new InternalError(e);
 126         }
 127         // Parse calendar.*.eras
 128         String props = calendarProps.getProperty("calendar." + name + ".eras");
 129         if (props == null) {
 130             return null;
 131         }
 132         List<Era> eras = new ArrayList<>();
 133         StringTokenizer eraTokens = new StringTokenizer(props, ";");
 134         while (eraTokens.hasMoreTokens()) {
 135             String items = eraTokens.nextToken().trim();
 136             StringTokenizer itemTokens = new StringTokenizer(items, ",");
 137             String eraName = null;
 138             boolean localTime = true;
 139             long since = 0;
 140             String abbr = null;
 141 
 142             while (itemTokens.hasMoreTokens()) {
 143                 String item = itemTokens.nextToken();
 144                 int index = item.indexOf('=');
 145                 // it must be in the key=value form.
 146                 if (index == -1) {
 147                     return null;
 148                 }
 149                 String key = item.substring(0, index);
 150                 String value = item.substring(index + 1);
 151                 if ("name".equals(key)) {
 152                     eraName = value;
 153                 } else if ("since".equals(key)) {
 154                     if (value.endsWith("u")) {
 155                         localTime = false;
 156                         since = Long.parseLong(value.substring(0, value.length() - 1));
 157                     } else {
 158                         since = Long.parseLong(value);
 159                     }
 160                 } else if ("abbr".equals(key)) {
 161                     abbr = value;
 162                 } else {
 163                     throw new RuntimeException("Unknown key word: " + key);
 164                 }
 165             }
 166             Era era = new Era(eraName, abbr, since, localTime);
 167             eras.add(era);
 168         }
 169         Era[] eraArray = new Era[eras.size()];
 170         eras.toArray(eraArray);
 171 
 172         return new LocalGregorianCalendar(name, eraArray);
 173     }
 174 
 175     private LocalGregorianCalendar(String name, Era[] eras) {
 176         this.name = name;
 177         this.eras = eras;
 178         setEras(eras);
 179     }
 180 
 181     @Override
 182     public String getName() {
 183         return name;
 184     }
 185 
 186     @Override
 187     public Date getCalendarDate() {
 188         return getCalendarDate(System.currentTimeMillis(), newCalendarDate());
 189     }
 190 
 191     @Override
 192     public Date getCalendarDate(long millis) {
 193         return getCalendarDate(millis, newCalendarDate());
 194     }
 195 
 196     @Override
 197     public Date getCalendarDate(long millis, TimeZone zone) {
 198         return getCalendarDate(millis, newCalendarDate(zone));
 199     }
 200 
 201     @Override
 202     public Date getCalendarDate(long millis, CalendarDate date) {
 203         Date ldate = (Date) super.getCalendarDate(millis, date);
 204         return adjustYear(ldate, millis, ldate.getZoneOffset());
 205     }
 206 
 207     private Date adjustYear(Date ldate, long millis, int zoneOffset) {
 208         int i;
 209         for (i = eras.length - 1; i >= 0; --i) {
 210             Era era = eras[i];
 211             long since = era.getSince(null);
 212             if (era.isLocalTime()) {
 213                 since -= zoneOffset;
 214             }
 215             if (millis >= since) {
 216                 ldate.setLocalEra(era);
 217                 int y = ldate.getNormalizedYear() - era.getSinceDate().getYear() + 1;
 218                 ldate.setLocalYear(y);
 219                 break;
 220             }
 221         }
 222         if (i < 0) {
 223             ldate.setLocalEra(null);
 224             ldate.setLocalYear(ldate.getNormalizedYear());
 225         }
 226         ldate.setNormalized(true);
 227         return ldate;
 228     }
 229 
 230     @Override
 231     public Date newCalendarDate() {
 232         return new Date();
 233     }
 234 
 235     @Override
 236     public Date newCalendarDate(TimeZone zone) {
 237         return new Date(zone);
 238     }
 239 
 240     @Override
 241     public boolean validate(CalendarDate date) {
 242         Date ldate = (Date) date;
 243         Era era = ldate.getEra();
 244         if (era != null) {
 245             if (!validateEra(era)) {
 246                 return false;
 247             }
 248             ldate.setNormalizedYear(era.getSinceDate().getYear() + ldate.getYear() - 1);
 249             Date tmp = newCalendarDate(date.getZone());
 250             tmp.setEra(era).setDate(date.getYear(), date.getMonth(), date.getDayOfMonth());
 251             normalize(tmp);
 252             if (tmp.getEra() != era) {
 253                 return false;
 254             }
 255         } else {
 256             if (date.getYear() >= eras[0].getSinceDate().getYear()) {
 257                 return false;
 258             }
 259             ldate.setNormalizedYear(ldate.getYear());
 260         }
 261         return super.validate(ldate);
 262     }
 263 
 264     private boolean validateEra(Era era) {
 265         // Validate the era
 266         for (int i = 0; i < eras.length; i++) {
 267             if (era == eras[i]) {
 268                 return true;
 269             }
 270         }
 271         return false;
 272     }
 273 
 274     @Override
 275     public boolean normalize(CalendarDate date) {
 276         if (date.isNormalized()) {
 277             return true;
 278         }
 279 
 280         normalizeYear(date);
 281         Date ldate = (Date) date;
 282 
 283         // Normalize it as a Gregorian date and get its millisecond value
 284         super.normalize(ldate);
 285 
 286         boolean hasMillis = false;
 287         long millis = 0;
 288         int year = ldate.getNormalizedYear();
 289         int i;
 290         Era era = null;
 291         for (i = eras.length - 1; i >= 0; --i) {
 292             era = eras[i];
 293             if (era.isLocalTime()) {
 294                 CalendarDate sinceDate = era.getSinceDate();
 295                 int sinceYear = sinceDate.getYear();
 296                 if (year > sinceYear) {
 297                     break;
 298                 }
 299                 if (year == sinceYear) {
 300                     int month = ldate.getMonth();
 301                     int sinceMonth = sinceDate.getMonth();
 302                     if (month > sinceMonth) {
 303                         break;
 304                     }
 305                     if (month == sinceMonth) {
 306                         int day = ldate.getDayOfMonth();
 307                         int sinceDay = sinceDate.getDayOfMonth();
 308                         if (day > sinceDay) {
 309                             break;
 310                         }
 311                         if (day == sinceDay) {
 312                             long timeOfDay = ldate.getTimeOfDay();
 313                             long sinceTimeOfDay = sinceDate.getTimeOfDay();
 314                             if (timeOfDay >= sinceTimeOfDay) {
 315                                 break;
 316                             }
 317                             --i;
 318                             break;
 319                         }
 320                     }
 321                 }
 322             } else {
 323                 if (!hasMillis) {
 324                     millis  = super.getTime(date);
 325                     hasMillis = true;
 326                 }
 327 
 328                 long since = era.getSince(date.getZone());
 329                 if (millis >= since) {
 330                     break;
 331                 }
 332             }
 333         }
 334         if (i >= 0) {
 335             ldate.setLocalEra(era);
 336             int y = ldate.getNormalizedYear() - era.getSinceDate().getYear() + 1;
 337             ldate.setLocalYear(y);
 338         } else {
 339             // Set Gregorian year with no era
 340             ldate.setEra(null);
 341             ldate.setLocalYear(year);
 342             ldate.setNormalizedYear(year);
 343         }
 344         ldate.setNormalized(true);
 345         return true;
 346     }
 347 
 348     @Override
 349     void normalizeMonth(CalendarDate date) {
 350         normalizeYear(date);
 351         super.normalizeMonth(date);
 352     }
 353 
 354     void normalizeYear(CalendarDate date) {
 355         Date ldate = (Date) date;
 356         // Set the supposed-to-be-correct Gregorian year first
 357         // e.g., Showa 90 becomes 2015 (1926 + 90 - 1).
 358         Era era = ldate.getEra();
 359         if (era == null || !validateEra(era)) {
 360             ldate.setNormalizedYear(ldate.getYear());
 361         } else {
 362             ldate.setNormalizedYear(era.getSinceDate().getYear() + ldate.getYear() - 1);
 363         }
 364     }
 365 
 366     /**
 367      * Returns whether the specified Gregorian year is a leap year.
 368      * @see #isLeapYear(Era, int)
 369      */
 370     @Override
 371     public boolean isLeapYear(int gregorianYear) {
 372         return CalendarUtils.isGregorianLeapYear(gregorianYear);
 373     }
 374 
 375     public boolean isLeapYear(Era era, int year) {
 376         if (era == null) {
 377             return isLeapYear(year);
 378         }
 379         int gyear = era.getSinceDate().getYear() + year - 1;
 380         return isLeapYear(gyear);
 381     }
 382 
 383     @Override
 384     public void getCalendarDateFromFixedDate(CalendarDate date, long fixedDate) {
 385         Date ldate = (Date) date;
 386         super.getCalendarDateFromFixedDate(ldate, fixedDate);
 387         adjustYear(ldate, (fixedDate - EPOCH_OFFSET) * DAY_IN_MILLIS, 0);
 388     }
 389 }