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.zone.Ser; 13 14 import hunt.stream.DataInput; 15 import hunt.stream.DataOutput; 16 import hunt.stream.Externalizable; 17 import hunt.Exceptions; 18 // import hunt.io.InvalidClassException; 19 import hunt.stream.ObjectInput; 20 import hunt.stream.ObjectOutput; 21 // import hunt.io.StreamCorruptedException; 22 import hunt.time.ZoneOffset; 23 import hunt.time.zone.ZoneRules; 24 import hunt.time.zone.ZoneOffsetTransition; 25 import hunt.time.zone.ZoneOffsetTransitionRule; 26 27 /** 28 * The shared serialization delegate for this package. 29 * 30 * @implNote 31 * This class is mutable and should be created once per serialization. 32 * 33 * @serial include 34 * @since 1.8 35 */ 36 final class Ser : Externalizable { 37 38 /** 39 * Serialization version. 40 */ 41 private enum long serialVersionUID = -8885321777449118786L; 42 43 /** Type for ZoneRules. */ 44 enum byte ZRULES = 1; 45 /** Type for ZoneOffsetTransition. */ 46 enum byte ZOT = 2; 47 /** Type for ZoneOffsetTransition. */ 48 enum byte ZOTRULE = 3; 49 50 /** The type being serialized. */ 51 private byte type; 52 /** The object being serialized. */ 53 private Object object; 54 55 /** 56 * Constructor for deserialization. 57 */ 58 public this() { 59 } 60 61 /** 62 * Creates an instance for serialization. 63 * 64 * @param type the type 65 * @param object the object 66 */ 67 this(byte type, Object object) { 68 this.type = type; 69 this.object = object; 70 } 71 72 //----------------------------------------------------------------------- 73 /** 74 * Implements the {@code Externalizable} interface to write the object. 75 * @serialData 76 * Each serializable class is mapped to a type that is the first byte 77 * _in the stream. Refer to each class {@code writeReplace} 78 * serialized form for the value of the type and sequence of values for the type. 79 * 80 * !(ul) 81 * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneRules">ZoneRules.writeReplace</a> 82 * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransition">ZoneOffsetTransition.writeReplace</a> 83 * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransitionRule">ZoneOffsetTransitionRule.writeReplace</a> 84 * </ul> 85 * 86 * @param _out the data stream to write to, not null 87 */ 88 override 89 public void writeExternal(ObjectOutput _out) /*throws IOException*/ { 90 writeInternal(type, object, _out); 91 } 92 93 static void write(Object object, DataOutput _out) /*throws IOException*/ { 94 writeInternal(ZRULES, object, _out); 95 } 96 97 private static void writeInternal(byte type, Object object, DataOutput _out) /*throws IOException*/ { 98 _out.writeByte(type); 99 switch (type) { 100 case ZRULES: 101 (cast(ZoneRules) object).writeExternal(_out); 102 break; 103 case ZOT: 104 (cast(ZoneOffsetTransition) object).writeExternal(_out); 105 break; 106 case ZOTRULE: 107 (cast(ZoneOffsetTransitionRule) object).writeExternal(_out); 108 break; 109 default: 110 throw new InvalidClassException("Unknown serialized type"); 111 } 112 } 113 114 //----------------------------------------------------------------------- 115 /** 116 * Implements the {@code Externalizable} interface to read the object. 117 * @serialData 118 * The streamed type and parameters defined by the type's {@code writeReplace} 119 * method are read and passed to the corresponding static factory for the type 120 * to create a new instance. That instance is returned as the de-serialized 121 * {@code Ser} object. 122 * 123 * !(ul) 124 * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneRules">ZoneRules</a> 125 * - {@code ZoneRules.of(standardTransitions, standardOffsets, savingsInstantTransitions, wallOffsets, lastRules);} 126 * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransition">ZoneOffsetTransition</a> 127 * - {@code ZoneOffsetTransition of(LocalDateTime.ofEpochSecond(epochSecond), offsetBefore, offsetAfter);} 128 * !(li)<a href="{@docRoot}/serialized-form.html#hunt.time.zone.ZoneOffsetTransitionRule">ZoneOffsetTransitionRule</a> 129 * - {@code ZoneOffsetTransitionRule.of(month, dom, dow, time, timeEndOfDay, timeDefinition, standardOffset, offsetBefore, offsetAfter);} 130 * </ul> 131 * @param _in the data to read, not null 132 */ 133 override 134 public void readExternal(ObjectInput _in) /*throws IOException, ClassNotFoundException */{ 135 type = _in.readByte(); 136 object = readInternal(type, _in); 137 } 138 139 static Object read(DataInput _in) /*throws IOException, ClassNotFoundException*/ { 140 byte type = _in.readByte(); 141 return readInternal(type, _in); 142 } 143 144 private static Object readInternal(byte type, DataInput _in) /*throws IOException, ClassNotFoundException*/ { 145 switch (type) { 146 case ZRULES: 147 return ZoneRules.readExternal(_in); 148 case ZOT: 149 return ZoneOffsetTransition.readExternal(_in); 150 case ZOTRULE: 151 return ZoneOffsetTransitionRule.readExternal(_in); 152 default: 153 throw new Exception("Unknown serialized type"); 154 } 155 } 156 157 /** 158 * Returns the object that will replace this one. 159 * 160 * @return the read object, should never be null 161 */ 162 private Object readResolve() { 163 return object; 164 } 165 166 //----------------------------------------------------------------------- 167 /** 168 * Writes the state to the stream. 169 * 170 * @param offset the offset, not null 171 * @param _out the output stream, not null 172 * @throws IOException if an error occurs 173 */ 174 static void writeOffset(ZoneOffset offset, DataOutput _out) /*throws IOException*/ { 175 int offsetSecs = offset.getTotalSeconds(); 176 int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72 177 _out.writeByte(offsetByte); 178 if (offsetByte == 127) { 179 _out.writeInt(offsetSecs); 180 } 181 } 182 183 /** 184 * Reads the state from the stream. 185 * 186 * @param _in the input stream, not null 187 * @return the created object, not null 188 * @throws IOException if an error occurs 189 */ 190 static ZoneOffset readOffset(DataInput _in) /*throws IOException*/ { 191 int offsetByte = _in.readByte(); 192 return (offsetByte == 127 ? ZoneOffset.ofTotalSeconds(_in.readInt()) : ZoneOffset.ofTotalSeconds(offsetByte * 900)); 193 } 194 195 //----------------------------------------------------------------------- 196 /** 197 * Writes the state to the stream. 198 * 199 * @param epochSec the epoch seconds, not null 200 * @param _out the output stream, not null 201 * @throws IOException if an error occurs 202 */ 203 static void writeEpochSec(long epochSec, DataOutput _out) /*throws IOException*/ { 204 if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) { // quarter hours between 1825 and 2300 205 int store = cast(int) ((epochSec + 4575744000L) / 900); 206 _out.writeByte((store >>> 16) & 255); 207 _out.writeByte((store >>> 8) & 255); 208 _out.writeByte(store & 255); 209 } else { 210 _out.writeByte(255); 211 _out.writeLong(epochSec); 212 } 213 } 214 215 /** 216 * Reads the state from the stream. 217 * 218 * @param _in the input stream, not null 219 * @return the epoch seconds, not null 220 * @throws IOException if an error occurs 221 */ 222 static long readEpochSec(DataInput _in) /*throws IOException*/ { 223 int hiByte = _in.readByte() & 255; 224 if (hiByte == 255) { 225 return _in.readLong(); 226 } else { 227 int midByte = _in.readByte() & 255; 228 int loByte = _in.readByte() & 255; 229 long tot = ((hiByte << 16) + (midByte << 8) + loByte); 230 return (tot * 900) - 4575744000L; 231 } 232 } 233 234 }