1 /*
   2  * Copyright (c) 2005, 2019, 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.security.AccessController;
  29 import java.util.TimeZone;
  30 import java.util.regex.Matcher;
  31 import java.util.regex.Pattern;
  32 import sun.security.action.GetPropertyAction;
  33 
  34 /**
  35  *
  36  * @author Masayoshi Okutsu
  37  * @since 1.6
  38  */
  39 
  40 public class LocalGregorianCalendar extends BaseCalendar {
  41     private static final Era[] JAPANESE_ERAS = {
  42         new Era("Meiji",  "M", -3218832000000L, true),
  43         new Era("Taisho", "T", -1812153600000L, true),
  44         new Era("Showa",  "S", -1357603200000L, true),
  45         new Era("Heisei", "H",   600220800000L, true),
  46         new Era("Reiwa",  "R",  1556668800000L, true),
  47     };
  48 
  49     private static boolean isValidEra(Era newEra, Era[] eras) {
  50         Era last = eras[eras.length - 1];
  51         if (last.getSince(null) >= newEra.getSince(null)) {
  52             return false;
  53         }
  54         // The new era name should be unique. Its abbr may not.
  55         String newName = newEra.getName();
  56         for (Era era : eras) {
  57             if (era.getName().equals(newName)) {
  58                 return false;
  59             }
  60         }
  61         return true;
  62     }
  63 
  64     private String name;
  65     private Era[] eras;
  66 
  67     public static class Date extends BaseCalendar.Date {
  68 
  69         protected Date() {
  70             super();
  71         }
  72 
  73         protected Date(TimeZone zone) {
  74             super(zone);
  75         }
  76 
  77         private int gregorianYear = FIELD_UNDEFINED;
  78 
  79         @Override
  80         public Date setEra(Era era) {
  81             if (getEra() != era) {
  82                 super.setEra(era);
  83                 gregorianYear = FIELD_UNDEFINED;
  84             }
  85             return this;
  86         }
  87 
  88         @Override
  89         public Date addYear(int localYear) {
  90             super.addYear(localYear);
  91             gregorianYear += localYear;
  92             return this;
  93         }
  94 
  95         @Override
  96         public Date setYear(int localYear) {
  97             if (getYear() != localYear) {
  98                 super.setYear(localYear);
  99                 gregorianYear = FIELD_UNDEFINED;
 100             }
 101             return this;
 102         }
 103 
 104         @Override
 105         public int getNormalizedYear() {
 106             return gregorianYear;
 107         }
 108 
 109         @Override
 110         public void setNormalizedYear(int normalizedYear) {
 111             this.gregorianYear = normalizedYear;
 112         }
 113 
 114         void setLocalEra(Era era) {
 115             super.setEra(era);
 116         }
 117 
 118         void setLocalYear(int year) {
 119             super.setYear(year);
 120         }
 121 
 122         @Override
 123         public String toString() {
 124             String time = super.toString();
 125             time = time.substring(time.indexOf('T'));
 126             StringBuffer sb = new StringBuffer();
 127             Era era = getEra();
 128             if (era != null) {
 129                 String abbr = era.getAbbreviation();
 130                 if (abbr != null) {
 131                     sb.append(abbr);
 132                 }
 133             }
 134             sb.append(getYear()).append('.');
 135             CalendarUtils.sprintf0d(sb, getMonth(), 2).append('.');
 136             CalendarUtils.sprintf0d(sb, getDayOfMonth(), 2);
 137             sb.append(time);
 138             return sb.toString();
 139         }
 140     }
 141 
 142     static LocalGregorianCalendar getLocalGregorianCalendar(String name) {
 143         // Only the Japanese calendar is supported.
 144         if (!"japanese".equals(name)) {
 145             return null;
 146         }
 147 
 148         // Append an era to the predefined eras if it's given by the property.
 149         String prop = GetPropertyAction
 150                 .privilegedGetProperty("jdk.calendar.japanese.supplemental.era");
 151         if (prop != null) {
 152             Era era = parseEraEntry(prop);
 153             if (era != null) {
 154                 if (isValidEra(era, JAPANESE_ERAS)) {
 155                     int length = JAPANESE_ERAS.length;
 156                     Era[] eras = new Era[length + 1];
 157                     System.arraycopy(JAPANESE_ERAS, 0, eras, 0, length);
 158                     eras[length] = era;
 159                     return new LocalGregorianCalendar(name, eras);
 160                 }
 161             }
 162         }
 163         return new LocalGregorianCalendar(name, JAPANESE_ERAS);
 164     }
 165 
 166     private static Era parseEraEntry(String entry) {
 167         String[] keyValuePairs = entry.split(",");
 168         String eraName = null;
 169         boolean localTime = true;
 170         long since = 0;
 171         String abbr = null;
 172 
 173         for (String item : keyValuePairs) {
 174             String[] keyvalue = item.split("=");
 175             if (keyvalue.length != 2) {
 176                 return null;
 177             }
 178             String key = keyvalue[0].trim();
 179             String value = convertUnicodeEscape(keyvalue[1].trim());
 180             switch (key) {
 181             case "name":
 182                 eraName = value;
 183                 break;
 184             case "since":
 185                 if (value.endsWith("u")) {
 186                     localTime = false;
 187                     value = value.substring(0, value.length() - 1);
 188                 }
 189                 try {
 190                     since = Long.parseLong(value);
 191                 } catch (NumberFormatException e) {
 192                     return null;
 193                 }
 194                 break;
 195             case "abbr":
 196                 abbr = value;
 197                 break;
 198             default:
 199                 return null;
 200             }
 201         }
 202         if (eraName == null || eraName.isEmpty()
 203                 || abbr == null || abbr.isEmpty()) {
 204             return null;
 205         }
 206         return new Era(eraName, abbr, since, localTime);
 207     }
 208 
 209     private static String convertUnicodeEscape(String src) {
 210         Matcher m = Pattern.compile("\\\\u([0-9a-fA-F]{4})").matcher(src);
 211         StringBuilder sb = new StringBuilder();
 212         while (m.find()) {
 213             m.appendReplacement(sb,
 214                 Character.toString((char)Integer.parseUnsignedInt(m.group(1), 16)));
 215         }
 216         m.appendTail(sb);
 217         return sb.toString();
 218     }
 219 
 220     private LocalGregorianCalendar(String name, Era[] eras) {
 221         this.name = name;
 222         this.eras = eras;
 223         setEras(eras);
 224     }
 225 
 226     @Override
 227     public String getName() {
 228         return name;
 229     }
 230 
 231     @Override
 232     public Date getCalendarDate() {
 233         return getCalendarDate(System.currentTimeMillis(), newCalendarDate());
 234     }
 235 
 236     @Override
 237     public Date getCalendarDate(long millis) {
 238         return getCalendarDate(millis, newCalendarDate());
 239     }
 240 
 241     @Override
 242     public Date getCalendarDate(long millis, TimeZone zone) {
 243         return getCalendarDate(millis, newCalendarDate(zone));
 244     }
 245 
 246     @Override
 247     public Date getCalendarDate(long millis, CalendarDate date) {
 248         Date ldate = (Date) super.getCalendarDate(millis, date);
 249         return adjustYear(ldate, millis, ldate.getZoneOffset());
 250     }
 251 
 252     private Date adjustYear(Date ldate, long millis, int zoneOffset) {
 253         int i;
 254         for (i = eras.length - 1; i >= 0; --i) {
 255             Era era = eras[i];
 256             long since = era.getSince(null);
 257             if (era.isLocalTime()) {
 258                 since -= zoneOffset;
 259             }
 260             if (millis >= since) {
 261                 ldate.setLocalEra(era);
 262                 int y = ldate.getNormalizedYear() - era.getSinceDate().getYear() + 1;
 263                 ldate.setLocalYear(y);
 264                 break;
 265             }
 266         }
 267         if (i < 0) {
 268             ldate.setLocalEra(null);
 269             ldate.setLocalYear(ldate.getNormalizedYear());
 270         }
 271         ldate.setNormalized(true);
 272         return ldate;
 273     }
 274 
 275     @Override
 276     public Date newCalendarDate() {
 277         return new Date();
 278     }
 279 
 280     @Override
 281     public Date newCalendarDate(TimeZone zone) {
 282         return new Date(zone);
 283     }
 284 
 285     @Override
 286     public boolean validate(CalendarDate date) {
 287         Date ldate = (Date) date;
 288         Era era = ldate.getEra();
 289         if (era != null) {
 290             if (!validateEra(era)) {
 291                 return false;
 292             }
 293             ldate.setNormalizedYear(era.getSinceDate().getYear() + ldate.getYear() - 1);
 294             Date tmp = newCalendarDate(date.getZone());
 295             tmp.setEra(era).setDate(date.getYear(), date.getMonth(), date.getDayOfMonth());
 296             normalize(tmp);
 297             if (tmp.getEra() != era) {
 298                 return false;
 299             }
 300         } else {
 301             if (date.getYear() >= eras[0].getSinceDate().getYear()) {
 302                 return false;
 303             }
 304             ldate.setNormalizedYear(ldate.getYear());
 305         }
 306         return super.validate(ldate);
 307     }
 308 
 309     private boolean validateEra(Era era) {
 310         for (Era era1 : eras) {
 311             if (era == era1) {
 312                 return true;
 313             }
 314         }
 315         return false;
 316     }
 317 
 318     @Override
 319     public boolean normalize(CalendarDate date) {
 320         if (date.isNormalized()) {
 321             return true;
 322         }
 323 
 324         normalizeYear(date);
 325         Date ldate = (Date) date;
 326 
 327         // Normalize it as a Gregorian date and get its millisecond value
 328         super.normalize(ldate);
 329 
 330         boolean hasMillis = false;
 331         long millis = 0;
 332         int year = ldate.getNormalizedYear();
 333         int i;
 334         Era era = null;
 335         for (i = eras.length - 1; i >= 0; --i) {
 336             era = eras[i];
 337             if (era.isLocalTime()) {
 338                 CalendarDate sinceDate = era.getSinceDate();
 339                 int sinceYear = sinceDate.getYear();
 340                 if (year > sinceYear) {
 341                     break;
 342                 }
 343                 if (year == sinceYear) {
 344                     int month = ldate.getMonth();
 345                     int sinceMonth = sinceDate.getMonth();
 346                     if (month > sinceMonth) {
 347                         break;
 348                     }
 349                     if (month == sinceMonth) {
 350                         int day = ldate.getDayOfMonth();
 351                         int sinceDay = sinceDate.getDayOfMonth();
 352                         if (day > sinceDay) {
 353                             break;
 354                         }
 355                         if (day == sinceDay) {
 356                             long timeOfDay = ldate.getTimeOfDay();
 357                             long sinceTimeOfDay = sinceDate.getTimeOfDay();
 358                             if (timeOfDay >= sinceTimeOfDay) {
 359                                 break;
 360                             }
 361                             --i;
 362                             break;
 363                         }
 364                     }
 365                 }
 366             } else {
 367                 if (!hasMillis) {
 368                     millis  = super.getTime(date);
 369                     hasMillis = true;
 370                 }
 371 
 372                 long since = era.getSince(date.getZone());
 373                 if (millis >= since) {
 374                     break;
 375                 }
 376             }
 377         }
 378         if (i >= 0) {
 379             ldate.setLocalEra(era);
 380             @SuppressWarnings("null")
 381             int y = ldate.getNormalizedYear() - era.getSinceDate().getYear() + 1;
 382             ldate.setLocalYear(y);
 383         } else {
 384             // Set Gregorian year with no era
 385             ldate.setEra(null);
 386             ldate.setLocalYear(year);
 387             ldate.setNormalizedYear(year);
 388         }
 389         ldate.setNormalized(true);
 390         return true;
 391     }
 392 
 393     @Override
 394     void normalizeMonth(CalendarDate date) {
 395         normalizeYear(date);
 396         super.normalizeMonth(date);
 397     }
 398 
 399     void normalizeYear(CalendarDate date) {
 400         Date ldate = (Date) date;
 401         // Set the supposed-to-be-correct Gregorian year first
 402         // e.g., Showa 90 becomes 2015 (1926 + 90 - 1).
 403         Era era = ldate.getEra();
 404         if (era == null || !validateEra(era)) {
 405             ldate.setNormalizedYear(ldate.getYear());
 406         } else {
 407             ldate.setNormalizedYear(era.getSinceDate().getYear() + ldate.getYear() - 1);
 408         }
 409     }
 410 
 411     /**
 412      * Returns whether the specified Gregorian year is a leap year.
 413      * @see #isLeapYear(Era, int)
 414      */
 415     @Override
 416     public boolean isLeapYear(int gregorianYear) {
 417         return CalendarUtils.isGregorianLeapYear(gregorianYear);
 418     }
 419 
 420     public boolean isLeapYear(Era era, int year) {
 421         if (era == null) {
 422             return isLeapYear(year);
 423         }
 424         int gyear = era.getSinceDate().getYear() + year - 1;
 425         return isLeapYear(gyear);
 426     }
 427 
 428     @Override
 429     public void getCalendarDateFromFixedDate(CalendarDate date, long fixedDate) {
 430         Date ldate = (Date) date;
 431         super.getCalendarDateFromFixedDate(ldate, fixedDate);
 432         adjustYear(ldate, (fixedDate - EPOCH_OFFSET) * DAY_IN_MILLIS, 0);
 433     }
 434 }