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.ChronoLocalDateTimeImpl; 13 14 import hunt.time.temporal.ChronoField; 15 16 import hunt.Exceptions; 17 import hunt.stream.ObjectInput; 18 //import hunt.io.ObjectInputStream; 19 import hunt.stream.ObjectOutput; 20 import hunt.stream.Common; 21 import hunt.time.LocalTime; 22 import hunt.time.ZoneId; 23 import hunt.time.temporal.ChronoField; 24 import hunt.time.temporal.ChronoUnit; 25 import hunt.time.temporal.Temporal; 26 import hunt.time.temporal.TemporalAdjuster; 27 import hunt.time.temporal.TemporalField; 28 import hunt.time.temporal.TemporalUnit; 29 import hunt.time.temporal.ValueRange; 30 import hunt.time.chrono.ChronoLocalDate; 31 import hunt.time.chrono.ChronoLocalDateTime; 32 import hunt.time.chrono.Chronology; 33 import hunt.time.chrono.ChronoZonedDateTime; 34 import hunt.time.chrono.ChronoLocalDateImpl; 35 import hunt.Long; 36 import hunt.math.Helper; 37 import hunt.time.chrono.ChronoZonedDateTimeImpl; 38 import hunt.time.chrono.Ser; 39 import hunt.time.temporal.TemporalAmount; 40 // import hunt.time.format.DateTimeFormatter; 41 import hunt.time.Instant; 42 import hunt.time.ZoneOffset; 43 /** 44 * A date-time without a time-zone for the calendar neutral API. 45 * !(p) 46 * {@code ChronoLocalDateTime} is an immutable date-time object that represents a date-time, often 47 * viewed as year-month-day-hour-minute-second. This object can also access other 48 * fields such as day-of-year, day-of-week and week-of-year. 49 * !(p) 50 * This class stores all date and time fields, to a precision of nanoseconds. 51 * It does not store or represent a time-zone. For example, the value 52 * "2nd October 2007 at 13:45.30.123456789" can be stored _in an {@code ChronoLocalDateTime}. 53 * 54 * @implSpec 55 * This class is immutable and thread-safe. 56 * @serial 57 * @param !(D) the concrete type for the date of this date-time 58 * @since 1.8 59 */ 60 final class ChronoLocalDateTimeImpl(D = ChronoLocalDate) if(is(D : ChronoLocalDate)) 61 : ChronoLocalDateTime!(D), Temporal, TemporalAdjuster { //, Serializable 62 63 /** 64 * Serialization version. 65 */ 66 private enum long serialVersionUID = 4556003607393004514L; 67 /** 68 * Hours per day. 69 */ 70 enum int HOURS_PER_DAY = 24; 71 /** 72 * Minutes per hour. 73 */ 74 enum int MINUTES_PER_HOUR = 60; 75 /** 76 * Minutes per day. 77 */ 78 enum int MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY; 79 /** 80 * Seconds per minute. 81 */ 82 enum int SECONDS_PER_MINUTE = 60; 83 /** 84 * Seconds per hour. 85 */ 86 enum int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; 87 /** 88 * Seconds per day. 89 */ 90 enum int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY; 91 /** 92 * Milliseconds per day. 93 */ 94 enum long MILLIS_PER_DAY = SECONDS_PER_DAY * 1000L; 95 /** 96 * Microseconds per day. 97 */ 98 enum long MICROS_PER_DAY = SECONDS_PER_DAY * 1000_000L; 99 /** 100 * Nanos per second. 101 */ 102 enum long NANOS_PER_SECOND = 1000_000_000L; 103 /** 104 * Nanos per minute. 105 */ 106 enum long NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE; 107 /** 108 * Nanos per hour. 109 */ 110 enum long NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR; 111 /** 112 * Nanos per day. 113 */ 114 enum long NANOS_PER_DAY = NANOS_PER_HOUR * HOURS_PER_DAY; 115 116 /** 117 * The date part. 118 */ 119 private /*transient*/ D date; 120 /** 121 * The time part. 122 */ 123 private /*transient*/ LocalTime time; 124 125 //----------------------------------------------------------------------- 126 /** 127 * Obtains an instance of {@code ChronoLocalDateTime} from a date and time. 128 * 129 * @param date the local date, not null 130 * @param time the local time, not null 131 * @return the local date-time, not null 132 */ 133 static ChronoLocalDateTimeImpl!(R) of(R)(R date, LocalTime time) { 134 return new ChronoLocalDateTimeImpl!(R)(date, time); 135 } 136 137 /** 138 * Casts the {@code Temporal} to {@code ChronoLocalDateTime} ensuring it bas the specified chronology. 139 * 140 * @param chrono the chronology to check for, not null 141 * @param temporal a date-time to cast, not null 142 * @return the date-time checked and cast to {@code ChronoLocalDateTime}, not null 143 * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDateTimeImpl 144 * or the chronology is not equal this Chronology 145 */ 146 static ChronoLocalDateTimeImpl!(R) ensureValid(R)(Chronology chrono, Temporal temporal) { 147 /*@SuppressWarnings("unchecked")*/ 148 ChronoLocalDateTimeImpl!(R) other = cast(ChronoLocalDateTimeImpl!(R))temporal; 149 if ((chrono == other.getChronology()) == false) { 150 throw new ClassCastException("Chronology mismatch, required: " ~ chrono.getId() 151 ~ ", actual: " ~ other.getChronology().getId()); 152 } 153 return other; 154 } 155 156 /** 157 * Constructor. 158 * 159 * @param date the date part of the date-time, not null 160 * @param time the time part of the date-time, not null 161 */ 162 private this(D date, LocalTime time) { 163 assert(date, "date"); 164 assert(time, "time"); 165 this.date = date; 166 this.time = time; 167 } 168 169 /** 170 * Returns a copy of this date-time with the new date and time, checking 171 * to see if a new object is _in fact required. 172 * 173 * @param newDate the date of the new date-time, not null 174 * @param newTime the time of the new date-time, not null 175 * @return the date-time, not null 176 */ 177 private ChronoLocalDateTimeImpl!(D) _with(Temporal newDate, LocalTime newTime) { 178 if (date == newDate && time == newTime) { 179 return this; 180 } 181 // Validate that the new Temporal is a ChronoLocalDate (and not something else) 182 D cd = ChronoLocalDateImpl!(D).ensureValid!D(date.getChronology(), newDate); 183 return new ChronoLocalDateTimeImpl!(D)(cd, newTime); 184 } 185 186 //----------------------------------------------------------------------- 187 override 188 public D toLocalDate() { 189 return date; 190 } 191 192 override 193 public LocalTime toLocalTime() { 194 return time; 195 } 196 197 //----------------------------------------------------------------------- 198 override 199 public bool isSupported(TemporalField field) { 200 if (cast(ChronoField)(field) !is null) { 201 ChronoField f = cast(ChronoField) field; 202 return f.isDateBased() || f.isTimeBased(); 203 } 204 return field !is null && field.isSupportedBy(this); 205 } 206 207 override 208 bool isSupported(TemporalUnit unit) { 209 if (cast(ChronoUnit)(unit) !is null) { 210 return unit != ChronoUnit.FOREVER; 211 } 212 return unit !is null && unit.isSupportedBy(this); 213 } 214 215 override 216 ChronoLocalDateTime!(D) plus(TemporalAmount amount) { 217 return ChronoLocalDateTimeImpl!D.ensureValid!D(getChronology(), /* Temporal. */amount.addTo(this)); 218 } 219 220 override 221 ChronoLocalDateTime!(D) minus(TemporalAmount amount) { 222 return ChronoLocalDateTimeImpl!D.ensureValid!D(getChronology(), /* Temporal. */amount.subtractFrom(this)); 223 } 224 225 override 226 ChronoLocalDateTime!(D) minus(long amountToSubtract, TemporalUnit unit) { 227 return ChronoLocalDateTimeImpl!D.ensureValid!D(getChronology(), /* Temporal. */(amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit))); 228 } 229 override 230 Chronology getChronology() { 231 return toLocalDate().getChronology(); 232 } 233 234 override 235 Temporal adjustInto(Temporal temporal) { 236 return temporal 237 ._with(ChronoField.EPOCH_DAY, toLocalDate().toEpochDay()) 238 ._with(ChronoField.NANO_OF_DAY, toLocalTime().toNanoOfDay()); 239 } 240 // override 241 // string format(DateTimeFormatter formatter) { 242 // assert(formatter, "formatter"); 243 // return formatter.format(this); 244 // } 245 246 override 247 Instant toInstant(ZoneOffset offset) { 248 return Instant.ofEpochSecond(toEpochSecond(offset), toLocalTime().getNano()); 249 } 250 override 251 long toEpochSecond(ZoneOffset offset) { 252 assert(offset, "offset"); 253 long epochDay = toLocalDate().toEpochDay(); 254 long secs = epochDay * 86400 + toLocalTime().toSecondOfDay(); 255 secs -= offset.getTotalSeconds(); 256 return secs; 257 } 258 259 override 260 int compareTo(ChronoLocalDateTime!(ChronoLocalDate) other) { 261 int cmp = toLocalDate().compareTo(other.toLocalDate()); 262 if (cmp == 0) { 263 cmp = toLocalTime().compareTo(other.toLocalTime()); 264 if (cmp == 0) { 265 cmp = getChronology().compareTo(other.getChronology()); 266 } 267 } 268 return cmp; 269 } 270 override 271 int opCmp(ChronoLocalDateTime!(ChronoLocalDate) other) { 272 int cmp = toLocalDate().compareTo(other.toLocalDate()); 273 if (cmp == 0) { 274 cmp = toLocalTime().compareTo(other.toLocalTime()); 275 if (cmp == 0) { 276 cmp = getChronology().compareTo(other.getChronology()); 277 } 278 } 279 return cmp; 280 } 281 override 282 bool isAfter(ChronoLocalDateTime!(ChronoLocalDate) other) { 283 long thisEpDay = this.toLocalDate().toEpochDay(); 284 long otherEpDay = other.toLocalDate().toEpochDay(); 285 return thisEpDay > otherEpDay || 286 (thisEpDay == otherEpDay && this.toLocalTime().toNanoOfDay() > other.toLocalTime().toNanoOfDay()); 287 } 288 override 289 bool isBefore(ChronoLocalDateTime!(ChronoLocalDate) other) { 290 long thisEpDay = this.toLocalDate().toEpochDay(); 291 long otherEpDay = other.toLocalDate().toEpochDay(); 292 return thisEpDay < otherEpDay || 293 (thisEpDay == otherEpDay && this.toLocalTime().toNanoOfDay() < other.toLocalTime().toNanoOfDay()); 294 } 295 override 296 bool isEqual(ChronoLocalDateTime!(ChronoLocalDate) other) { 297 // Do the time check first, it is cheaper than computing EPOCH day. 298 return this.toLocalTime().toNanoOfDay() == other.toLocalTime().toNanoOfDay() && 299 this.toLocalDate().toEpochDay() == other.toLocalDate().toEpochDay(); 300 } 301 302 override 303 public ValueRange range(TemporalField field) { 304 if (cast(ChronoField)(field) !is null) { 305 ChronoField f = cast(ChronoField) field; 306 return (f.isTimeBased() ? time.range(field) : date.range(field)); 307 } 308 return field.rangeRefinedBy(this); 309 } 310 311 override 312 public int get(TemporalField field) { 313 if (cast(ChronoField)(field) !is null) { 314 ChronoField f = cast(ChronoField) field; 315 return (f.isTimeBased() ? time.get(field) : date.get(field)); 316 } 317 return range(field).checkValidIntValue(getLong(field), field); 318 } 319 320 override 321 public long getLong(TemporalField field) { 322 if (cast(ChronoField)(field) !is null) { 323 ChronoField f = cast(ChronoField) field; 324 return (f.isTimeBased() ? time.getLong(field) : date.getLong(field)); 325 } 326 return field.getFrom(this); 327 } 328 329 //----------------------------------------------------------------------- 330 /*@SuppressWarnings("unchecked")*/ 331 override 332 public ChronoLocalDateTimeImpl!(D) _with(TemporalAdjuster adjuster) { 333 if (cast(ChronoLocalDate)(adjuster) !is null) { 334 // The Chronology is checked _in _with(date,time) 335 return _with(cast(ChronoLocalDate) adjuster, time); 336 } else if (cast(LocalTime)(adjuster) !is null) { 337 return _with(date, cast(LocalTime) adjuster); 338 } else if (cast(ChronoLocalDateTimeImpl!D)(adjuster) !is null) { 339 return ChronoLocalDateTimeImpl!D.ensureValid!D(date.getChronology(), cast(ChronoLocalDateTimeImpl!(ChronoLocalDate)) adjuster); 340 } 341 return ChronoLocalDateTimeImpl!D.ensureValid!D(date.getChronology(), cast(ChronoLocalDateTimeImpl!(ChronoLocalDate)) adjuster.adjustInto(this)); 342 } 343 344 override 345 public ChronoLocalDateTimeImpl!(D) _with(TemporalField field, long newValue) { 346 if (cast(ChronoField)(field) !is null) { 347 ChronoField f = cast(ChronoField) field; 348 if (f.isTimeBased()) { 349 return _with(date, time._with(field, newValue)); 350 } else { 351 return _with(date._with(field, newValue), time); 352 } 353 } 354 return ChronoLocalDateTimeImpl!D.ensureValid!D(date.getChronology(), field.adjustInto(this, newValue)); 355 } 356 357 //----------------------------------------------------------------------- 358 override 359 public ChronoLocalDateTimeImpl!(D) plus(long amountToAdd, TemporalUnit unit) { 360 if (cast(ChronoUnit)(unit) !is null) { 361 ChronoUnit f = cast(ChronoUnit) unit; 362 { 363 if( f == ChronoUnit.NANOS) return plusNanos(amountToAdd); 364 if( f == ChronoUnit.MICROS) return plusDays(amountToAdd / LocalTime.MICROS_PER_DAY).plusNanos((amountToAdd % LocalTime.MICROS_PER_DAY) * 1000); 365 if( f == ChronoUnit.MILLIS) return plusDays(amountToAdd / LocalTime.MILLIS_PER_DAY).plusNanos((amountToAdd % LocalTime.MILLIS_PER_DAY) * 1000000); 366 if( f == ChronoUnit.SECONDS) return plusSeconds(amountToAdd); 367 if( f == ChronoUnit.MINUTES) return plusMinutes(amountToAdd); 368 if( f == ChronoUnit.HOURS) return plusHours(amountToAdd); 369 if( f == ChronoUnit.HALF_DAYS) return plusDays(amountToAdd / 256).plusHours((amountToAdd % 256) * 12); // no overflow (256 is multiple of 2) 370 } 371 return _with(date.plus(amountToAdd, unit), time); 372 } 373 return ChronoLocalDateTimeImpl!D.ensureValid!D(date.getChronology(), unit.addTo(this, amountToAdd)); 374 } 375 376 private ChronoLocalDateTimeImpl!(D) plusDays(long days) { 377 return _with(date.plus(days, ChronoUnit.DAYS), time); 378 } 379 380 private ChronoLocalDateTimeImpl!(D) plusHours(long hours) { 381 return plusWithOverflow(date, hours, 0, 0, 0); 382 } 383 384 private ChronoLocalDateTimeImpl!(D) plusMinutes(long minutes) { 385 return plusWithOverflow(date, 0, minutes, 0, 0); 386 } 387 388 ChronoLocalDateTimeImpl!(D) plusSeconds(long seconds) { 389 return plusWithOverflow(date, 0, 0, seconds, 0); 390 } 391 392 private ChronoLocalDateTimeImpl!(D) plusNanos(long nanos) { 393 return plusWithOverflow(date, 0, 0, 0, nanos); 394 } 395 396 //----------------------------------------------------------------------- 397 private ChronoLocalDateTimeImpl!(D) plusWithOverflow(D newDate, long hours, long minutes, long seconds, long nanos) { 398 // 9223372036854775808 long, 2147483648 int 399 if ((hours | minutes | seconds | nanos) == 0) { 400 return _with(newDate, time); 401 } 402 long totDays = nanos / NANOS_PER_DAY + // max/24*60*60*1B 403 seconds / SECONDS_PER_DAY + // max/24*60*60 404 minutes / MINUTES_PER_DAY + // max/24*60 405 hours / HOURS_PER_DAY; // max/24 406 long totNanos = nanos % NANOS_PER_DAY + // max 86400000000000 407 (seconds % SECONDS_PER_DAY) * NANOS_PER_SECOND + // max 86400000000000 408 (minutes % MINUTES_PER_DAY) * NANOS_PER_MINUTE + // max 86400000000000 409 (hours % HOURS_PER_DAY) * NANOS_PER_HOUR; // max 86400000000000 410 long curNoD = time.toNanoOfDay(); // max 86400000000000 411 totNanos = totNanos + curNoD; // total 432000000000000 412 totDays += MathHelper.floorDiv(totNanos, NANOS_PER_DAY); 413 long newNoD = MathHelper.floorMod(totNanos, NANOS_PER_DAY); 414 LocalTime newTime = (newNoD == curNoD ? time : LocalTime.ofNanoOfDay(newNoD)); 415 return _with(newDate.plus(totDays, ChronoUnit.DAYS), newTime); 416 } 417 418 //----------------------------------------------------------------------- 419 override 420 public ChronoZonedDateTime!(D) atZone(ZoneId zone) { 421 return ChronoZonedDateTimeImpl!(D).ofBest!(D)(this, zone, null); 422 } 423 424 //----------------------------------------------------------------------- 425 override 426 public long until(Temporal endExclusive, TemporalUnit unit) { 427 assert(endExclusive, "endExclusive"); 428 /*@SuppressWarnings("unchecked")*/ 429 ChronoLocalDateTime!(D) end = cast(ChronoLocalDateTime!(D)) getChronology().localDateTime(endExclusive); 430 if (cast(ChronoUnit)(unit) !is null) { 431 if (unit.isTimeBased()) { 432 long amount = end.getLong(ChronoField.EPOCH_DAY) - date.getLong(ChronoField.EPOCH_DAY); 433 auto f = cast(ChronoUnit) unit; 434 { 435 if( f == ChronoUnit.NANOS) amount = MathHelper.multiplyExact(amount, NANOS_PER_DAY); 436 if( f == ChronoUnit.MICROS) amount = MathHelper.multiplyExact(amount, MICROS_PER_DAY); 437 if( f == ChronoUnit.MILLIS) amount = MathHelper.multiplyExact(amount, MILLIS_PER_DAY); 438 if( f == ChronoUnit.SECONDS) amount = MathHelper.multiplyExact(amount, SECONDS_PER_DAY); 439 if( f == ChronoUnit.MINUTES) amount = MathHelper.multiplyExact(amount, MINUTES_PER_DAY); 440 if( f == ChronoUnit.HOURS) amount = MathHelper.multiplyExact(amount, HOURS_PER_DAY); 441 if( f == ChronoUnit.HALF_DAYS) amount = MathHelper.multiplyExact(amount, 2); 442 } 443 return MathHelper.addExact(amount, time.until(end.toLocalTime(), unit)); 444 } 445 ChronoLocalDate endDate = end.toLocalDate(); 446 if (end.toLocalTime().isBefore(time)) { 447 endDate = endDate.minus(1, ChronoUnit.DAYS); 448 } 449 return date.until(endDate, unit); 450 } 451 assert(unit, "unit"); 452 return unit.between(this, end); 453 } 454 455 //----------------------------------------------------------------------- 456 /** 457 * Writes the ChronoLocalDateTime using a 458 * <a href="{@docRoot}/serialized-form.html#hunt.time.chrono.Ser">dedicated serialized form</a>. 459 * @serialData 460 * !(pre) 461 * _out.writeByte(2); // identifies a ChronoLocalDateTime 462 * _out.writeObject(toLocalDate()); 463 * _out.witeObject(toLocalTime()); 464 * </pre> 465 * 466 * @return the instance of {@code Ser}, not null 467 */ 468 private Object writeReplace() { 469 return new Ser(Ser.CHRONO_LOCAL_DATE_TIME_TYPE, this); 470 } 471 472 /** 473 * Defend against malicious streams. 474 * 475 * @param s the stream to read 476 * @throws InvalidObjectException always 477 */ 478 ///@gxc 479 // private void readObject(ObjectInputStream s) /*throws InvalidObjectException*/ { 480 // throw new InvalidObjectException("Deserialization via serialization delegate"); 481 // } 482 483 void writeExternal(ObjectOutput _out) /*throws IOException*/ { 484 _out.writeObject(cast(Object)date); 485 _out.writeObject(time); 486 } 487 488 static ChronoLocalDateTime!(ChronoLocalDate) readExternal(ObjectInput _in) /*throws IOException, ClassNotFoundException*/ { 489 ChronoLocalDate date = cast(ChronoLocalDate) _in.readObject(); 490 LocalTime time = cast(LocalTime) _in.readObject(); 491 return date.atTime(time); 492 } 493 494 //----------------------------------------------------------------------- 495 override 496 public bool opEquals(Object obj) { 497 if (this is obj) { 498 return true; 499 } 500 if (cast(ChronoLocalDateTime!D)(obj) !is null) { 501 return compareTo(cast(ChronoLocalDateTime!(D)) obj) == 0; 502 } 503 return false; 504 } 505 506 override 507 public size_t toHash() @trusted nothrow { 508 try{ 509 return toLocalDate().toHash() ^ toLocalTime().toHash(); 510 }catch(Exception e){} 511 return int.init; 512 } 513 514 override 515 public string toString() { 516 return toLocalDate().toString() ~ 'T' ~ toLocalTime().toString(); 517 } 518 519 }