1 /* 2 * hunt-time: A time library for D programming language. 3 * 4 * Copyright (C) 2015-2018 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.time.chrono.ChronoPeriodImpl; 13 14 import hunt.time.temporal.ChronoField; 15 import hunt.time.temporal.ChronoUnit; 16 17 import hunt.stream.DataInput; 18 import hunt.stream.DataOutput; 19 import hunt.Exceptions; 20 import hunt.collection; 21 //import hunt.io.ObjectInputStream; 22 // import hunt.io.ObjectStreamException; 23 import hunt.stream.Common; 24 import hunt.time.Exceptions; 25 import hunt.time.temporal.ChronoUnit; 26 import hunt.time.temporal.Temporal; 27 import hunt.time.temporal.TemporalAccessor; 28 import hunt.time.temporal.TemporalAmount; 29 import hunt.time.temporal.TemporalQueries; 30 import hunt.time.temporal.TemporalUnit; 31 import hunt.time.Exceptions; 32 import hunt.time.temporal.ValueRange; 33 import hunt.collection.List; 34 import hunt.time.chrono.Chronology; 35 import hunt.time.chrono.ChronoPeriod; 36 import hunt.Integer; 37 import hunt.math.Helper; 38 import hunt.util.StringBuilder; 39 import hunt.time.chrono.Ser; 40 import hunt.time.util.QueryHelper; 41 42 import hunt.util.Common; 43 // import hunt.serialization.JsonSerializer; 44 45 46 /** 47 * A period expressed _in terms of a standard year-month-day calendar system. 48 * !(p) 49 * This class is used by applications seeking to handle dates _in non-ISO calendar systems. 50 * For example, the Japanese, Minguo, Thai Buddhist and others. 51 * 52 * @implSpec 53 * This class is immutable nad thread-safe. 54 * 55 * @since 1.8 56 */ 57 final class ChronoPeriodImpl 58 : ChronoPeriod { // , Serializable 59 // this class is only used by JDK chronology implementations and makes assumptions based on that fact 60 61 62 /** 63 * The set of supported units. 64 */ 65 __gshared List!(TemporalUnit) _SUPPORTED_UNITS; 66 67 public static ref List!(TemporalUnit) SUPPORTED_UNITS() 68 { 69 if(_SUPPORTED_UNITS is null) 70 { 71 _SUPPORTED_UNITS = new ArrayList!TemporalUnit(); 72 _SUPPORTED_UNITS.add(ChronoUnit.YEARS); 73 _SUPPORTED_UNITS.add(ChronoUnit.MONTHS); 74 _SUPPORTED_UNITS.add(ChronoUnit.DAYS); 75 } 76 return _SUPPORTED_UNITS; 77 } 78 79 // shared static this() 80 // { 81 // SUPPORTED_UNITS = new ArrayList!TemporalUnit(); 82 // SUPPORTED_UNITS.add(ChronoUnit.YEARS); 83 // SUPPORTED_UNITS.add(ChronoUnit.MONTHS); 84 // SUPPORTED_UNITS.add(ChronoUnit.DAYS); 85 // } 86 /** 87 * The chronology. 88 */ 89 private Chronology chrono; 90 /** 91 * The number of years. 92 */ 93 int years; 94 /** 95 * The number of months. 96 */ 97 int months; 98 /** 99 * The number of days. 100 */ 101 int days; 102 103 /** 104 * Creates an instance. 105 */ 106 this(Chronology chrono, int years, int months, int days) { 107 assert(chrono, "chrono"); 108 this.chrono = chrono; 109 this.years = years; 110 this.months = months; 111 this.days = days; 112 } 113 114 //----------------------------------------------------------------------- 115 override 116 public long get(TemporalUnit unit) { 117 if (unit == ChronoUnit.YEARS) { 118 return years; 119 } else if (unit == ChronoUnit.MONTHS) { 120 return months; 121 } else if (unit == ChronoUnit.DAYS) { 122 return days; 123 } else { 124 throw new UnsupportedTemporalTypeException("Unsupported unit: " ~ typeid(unit).name); 125 } 126 } 127 128 override 129 public List!(TemporalUnit) getUnits() { 130 return ChronoPeriodImpl.SUPPORTED_UNITS; 131 } 132 133 override 134 public Chronology getChronology() { 135 return chrono; 136 } 137 138 //----------------------------------------------------------------------- 139 override 140 public bool isZero() { 141 return years == 0 && months == 0 && days == 0; 142 } 143 144 override 145 public bool isNegative() { 146 return years < 0 || months < 0 || days < 0; 147 } 148 149 //----------------------------------------------------------------------- 150 override 151 public ChronoPeriod plus(TemporalAmount amountToAdd) { 152 ChronoPeriodImpl amount = validateAmount(amountToAdd); 153 return new ChronoPeriodImpl( 154 chrono, 155 MathHelper.addExact(years, amount.years), 156 MathHelper.addExact(months, amount.months), 157 MathHelper.addExact(days, amount.days)); 158 } 159 160 override 161 public ChronoPeriod minus(TemporalAmount amountToSubtract) { 162 ChronoPeriodImpl amount = validateAmount(amountToSubtract); 163 return new ChronoPeriodImpl( 164 chrono, 165 MathHelper.subtractExact(years, amount.years), 166 MathHelper.subtractExact(months, amount.months), 167 MathHelper.subtractExact(days, amount.days)); 168 } 169 170 /** 171 * Obtains an instance of {@code ChronoPeriodImpl} from a temporal amount. 172 * 173 * @param amount the temporal amount to convert, not null 174 * @return the period, not null 175 */ 176 private ChronoPeriodImpl validateAmount(TemporalAmount amount) { 177 assert(amount, "amount"); 178 if ((cast(ChronoPeriodImpl)(amount) !is null) == false) { 179 throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " ~ typeid(amount).stringof); 180 } 181 ChronoPeriodImpl period = cast(ChronoPeriodImpl) amount; 182 if ((chrono == period.getChronology()) == false) { 183 throw new ClassCastException("Chronology mismatch, expected: " ~ chrono.getId() ~ ", actual: " ~ period.getChronology().getId()); 184 } 185 return period; 186 } 187 188 //----------------------------------------------------------------------- 189 override 190 public ChronoPeriod multipliedBy(int scalar) { 191 if (this.isZero() || scalar == 1) { 192 return this; 193 } 194 return new ChronoPeriodImpl( 195 chrono, 196 MathHelper.multiplyExact(years, scalar), 197 MathHelper.multiplyExact(months, scalar), 198 MathHelper.multiplyExact(days, scalar)); 199 } 200 201 //----------------------------------------------------------------------- 202 override 203 public ChronoPeriod normalized() { 204 long monthRange = monthRange(); 205 if (monthRange > 0) { 206 long totalMonths = years * monthRange + months; 207 long splitYears = totalMonths / monthRange; 208 int splitMonths = cast(int) (totalMonths % monthRange); // no overflow 209 if (splitYears == years && splitMonths == months) { 210 return this; 211 } 212 return new ChronoPeriodImpl(chrono, MathHelper.toIntExact(splitYears), splitMonths, days); 213 214 } 215 return this; 216 } 217 218 override 219 ChronoPeriod negated() { 220 return multipliedBy(-1); 221 } 222 /** 223 * Calculates the range of months. 224 * 225 * @return the month range, -1 if not fixed range 226 */ 227 private long monthRange() { 228 ValueRange startRange = chrono.range(ChronoField.MONTH_OF_YEAR); 229 if (startRange.isFixed() && startRange.isIntValue()) { 230 return startRange.getMaximum() - startRange.getMinimum() + 1; 231 } 232 return -1; 233 } 234 235 //------------------------------------------------------------------------- 236 override 237 public Temporal addTo(Temporal temporal) { 238 validateChrono(temporal); 239 if (months == 0) { 240 if (years != 0) { 241 temporal = temporal.plus(years, ChronoUnit.YEARS); 242 } 243 } else { 244 long monthRange = monthRange(); 245 if (monthRange > 0) { 246 temporal = temporal.plus(years * monthRange + months, ChronoUnit.MONTHS); 247 } else { 248 if (years != 0) { 249 temporal = temporal.plus(years, ChronoUnit.YEARS); 250 } 251 temporal = temporal.plus(months, ChronoUnit.MONTHS); 252 } 253 } 254 if (days != 0) { 255 temporal = temporal.plus(days, ChronoUnit.DAYS); 256 } 257 return temporal; 258 } 259 260 261 262 override 263 public Temporal subtractFrom(Temporal temporal) { 264 validateChrono(temporal); 265 if (months == 0) { 266 if (years != 0) { 267 temporal = temporal.minus(years, ChronoUnit.YEARS); 268 } 269 } else { 270 long monthRange = monthRange(); 271 if (monthRange > 0) { 272 temporal = temporal.minus(years * monthRange + months, ChronoUnit.MONTHS); 273 } else { 274 if (years != 0) { 275 temporal = temporal.minus(years, ChronoUnit.YEARS); 276 } 277 temporal = temporal.minus(months, ChronoUnit.MONTHS); 278 } 279 } 280 if (days != 0) { 281 temporal = temporal.minus(days, ChronoUnit.DAYS); 282 } 283 return temporal; 284 } 285 286 /** 287 * Validates that the temporal has the correct chronology. 288 */ 289 private void validateChrono(TemporalAccessor temporal) { 290 assert(temporal, "temporal"); 291 Chronology temporalChrono = QueryHelper.query!Chronology(temporal , TemporalQueries.chronology()); 292 if (temporalChrono !is null && (chrono == temporalChrono) == false) { 293 throw new DateTimeException("Chronology mismatch, expected: " ~ chrono.getId() ~ ", actual: " ~ temporalChrono.getId()); 294 } 295 } 296 297 //----------------------------------------------------------------------- 298 override 299 public bool opEquals(Object obj) { 300 if (this is obj) { 301 return true; 302 } 303 if (cast(ChronoPeriodImpl)(obj) !is null) { 304 ChronoPeriodImpl other = cast(ChronoPeriodImpl) obj; 305 return years == other.years && months == other.months && 306 days == other.days && chrono == (other.chrono); 307 } 308 return false; 309 } 310 311 override 312 public size_t toHash() @trusted nothrow { 313 try{ 314 return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.toHash(); 315 } 316 catch(Exception e){} 317 return int.init; 318 } 319 320 //----------------------------------------------------------------------- 321 override 322 public string toString() { 323 if (isZero()) { 324 return getChronology().toString() ~ " P0D"; 325 } else { 326 StringBuilder buf = new StringBuilder(); 327 buf.append(getChronology().toString()).append(' ').append('P'); 328 if (years != 0) { 329 buf.append(years).append('Y'); 330 } 331 if (months != 0) { 332 buf.append(months).append('M'); 333 } 334 if (days != 0) { 335 buf.append(days).append('D'); 336 } 337 return buf.toString(); 338 } 339 } 340 341 //----------------------------------------------------------------------- 342 /** 343 * Writes the Chronology using a 344 * <a href="{@docRoot}/serialized-form.html#hunt.time.chrono.Ser">dedicated serialized form</a>. 345 * !(pre) 346 * _out.writeByte(12); // identifies this as a ChronoPeriodImpl 347 * _out.writeUTF(getId()); // the chronology 348 * _out.writeInt(years); 349 * _out.writeInt(months); 350 * _out.writeInt(days); 351 * </pre> 352 * 353 * @return the instance of {@code Ser}, not null 354 */ 355 protected Object writeReplace() { 356 return new Ser(Ser.CHRONO_PERIOD_TYPE, this); 357 } 358 359 /** 360 * Defend against malicious streams. 361 * 362 * @param s the stream to read 363 * @throws InvalidObjectException always 364 */ 365 ///@gxc 366 // private void readObject(ObjectInputStream s) /*throws ObjectStreamException*/ { 367 // throw new InvalidObjectException("Deserialization via serialization delegate"); 368 // } 369 370 void writeExternal(DataOutput _out) /*throws IOException*/ { 371 _out.writeUTF(chrono.getId()); 372 _out.writeInt(years); 373 _out.writeInt(months); 374 _out.writeInt(days); 375 } 376 377 static ChronoPeriodImpl readExternal(DataInput _in) /*throws IOException*/ { 378 Chronology chrono = Chronology.of(_in.readUTF()); 379 int years = _in.readInt(); 380 int months = _in.readInt(); 381 int days = _in.readInt(); 382 return new ChronoPeriodImpl(chrono, years, months, days); 383 } 384 385 // mixin SerializationMember!(typeof(this)); 386 }