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 }