1 module hunt.time.format.ReducedPrinterParser; 2 3 import hunt.time.Exceptions; 4 import hunt.time.LocalDate; 5 6 import hunt.time.chrono.Chronology; 7 import hunt.time.chrono.ChronoLocalDate; 8 9 import hunt.time.format.DateTimeParseContext; 10 import hunt.time.format.DateTimePrinterParser; 11 import hunt.time.format.DateTimePrintContext; 12 import hunt.time.format.NumberPrinterParser; 13 import hunt.time.format.SignStyle; 14 import hunt.time.temporal.TemporalField; 15 import hunt.time.util.Common; 16 import hunt.util.StringBuilder; 17 18 import hunt.Exceptions; 19 import hunt.Integer; 20 import hunt.math.Helper; 21 import hunt.Functions; 22 23 import std.conv; 24 25 //----------------------------------------------------------------------- 26 /** 27 * Prints and parses a reduced numeric date-time field. 28 */ 29 final class ReducedPrinterParser : NumberPrinterParser 30 { 31 /** 32 * The base date for reduced value parsing. 33 */ 34 // __gshared LocalDate BASE_DATE; 35 36 private int baseValue; 37 private ChronoLocalDate baseDate; 38 39 // shared static this() 40 // { 41 // BASE_DATE = LocalDate.of(2000, 1, 1); 42 mixin(MakeGlobalVar!(LocalDate)("BASE_DATE",`LocalDate.of(2000, 1, 1)`)); 43 // } 44 45 /** 46 * Constructor. 47 * 48 * @param field the field to format, validated not null 49 * @param minWidth the minimum field width, from 1 to 10 50 * @param maxWidth the maximum field width, from 1 to 10 51 * @param baseValue the base value 52 * @param baseDate the base date 53 */ 54 this(TemporalField field, int minWidth, int maxWidth, int baseValue, 55 ChronoLocalDate baseDate) 56 { 57 this(field, minWidth, maxWidth, baseValue, baseDate, 0); 58 if (minWidth < 1 || minWidth > 10) 59 { 60 throw new IllegalArgumentException( 61 "The minWidth must be from 1 to 10 inclusive but was " ~ minWidth 62 .to!string); 63 } 64 if (maxWidth < 1 || maxWidth > 10) 65 { 66 throw new IllegalArgumentException( 67 "The maxWidth must be from 1 to 10 inclusive but was " ~ minWidth 68 .to!string); 69 } 70 if (maxWidth < minWidth) 71 { 72 throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " 73 ~ maxWidth.to!string ~ " < " ~ minWidth.to!string); 74 } 75 if (baseDate is null) 76 { 77 if (field.range().isValidValue(baseValue) == false) 78 { 79 throw new IllegalArgumentException( 80 "The base value must be within the range of the field"); 81 } 82 if (((cast(long) baseValue) + EXCEED_POINTS[maxWidth]) > Integer.MAX_VALUE) 83 { 84 throw new DateTimeException( 85 "Unable to add printer-parser as the range exceeds the capacity of an int"); 86 } 87 } 88 } 89 90 /** 91 * Constructor. 92 * The arguments have already been checked. 93 * 94 * @param field the field to format, validated not null 95 * @param minWidth the minimum field width, from 1 to 10 96 * @param maxWidth the maximum field width, from 1 to 10 97 * @param baseValue the base value 98 * @param baseDate the base date 99 * @param subsequentWidth the subsequentWidth for this instance 100 */ 101 package this(TemporalField field, int minWidth, int maxWidth, 102 int baseValue, ChronoLocalDate baseDate, int subsequentWidth) 103 { 104 super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth); 105 this.baseValue = baseValue; 106 this.baseDate = baseDate; 107 } 108 109 override long getValue(DateTimePrintContext context, long value) 110 { 111 long absValue = MathHelper.abs(value); 112 int baseValue = this.baseValue; 113 if (baseDate !is null) 114 { 115 Chronology chrono = Chronology.from(context.getTemporal()); 116 baseValue = chrono.date(baseDate).get(field); 117 } 118 if (value >= baseValue && value < baseValue + EXCEED_POINTS[minWidth]) 119 { 120 // Use the reduced value if it fits _in minWidth 121 return absValue % EXCEED_POINTS[minWidth]; 122 } 123 // Otherwise truncate to fit _in maxWidth 124 return absValue % EXCEED_POINTS[maxWidth]; 125 } 126 127 override int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) 128 { 129 int baseValue = this.baseValue; 130 if (baseDate !is null) 131 { 132 Chronology chrono = context.getEffectiveChronology(); 133 baseValue = chrono.date(baseDate).get(field); 134 135 // In case the Chronology is changed later, add a callback when/if it changes 136 long initialValue = value; 137 context.addChronoChangedListener( (Chronology t) { 138 /* Repeat the set of the field using the current Chronology 139 * The success/error position is ignored because the value is 140 * intentionally being overwritten. 141 */ 142 setValue(context, initialValue, errorPos, successPos); 143 }); 144 } 145 int parseLen = successPos - errorPos; 146 if (parseLen == minWidth && value >= 0) 147 { 148 long range = EXCEED_POINTS[minWidth]; 149 long lastPart = baseValue % range; 150 long basePart = baseValue - lastPart; 151 if (baseValue > 0) 152 { 153 value = basePart + value; 154 } 155 else 156 { 157 value = basePart - value; 158 } 159 if (value < baseValue) 160 { 161 value += range; 162 } 163 } 164 return context.setParsedField(field, value, errorPos, successPos); 165 } 166 167 /** 168 * Returns a new instance with fixed width flag set. 169 * 170 * @return a new updated printer-parser, not null 171 */ 172 override ReducedPrinterParser withFixedWidth() 173 { 174 if (subsequentWidth == -1) 175 { 176 return this; 177 } 178 return new ReducedPrinterParser(field, minWidth, maxWidth, 179 baseValue, baseDate, -1); 180 } 181 182 /** 183 * Returns a new instance with an updated subsequent width. 184 * 185 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater 186 * @return a new updated printer-parser, not null 187 */ 188 override ReducedPrinterParser withSubsequentWidth(int subsequentWidth) 189 { 190 return new ReducedPrinterParser(field, minWidth, maxWidth, 191 baseValue, baseDate, this.subsequentWidth + subsequentWidth); 192 } 193 194 /** 195 * For a ReducedPrinterParser, fixed width is false if the mode is strict, 196 * otherwise it is set as for NumberPrinterParser. 197 * @param context the context 198 * @return if the field is fixed width 199 * @see DateTimeFormatterBuilder#appendValueReduced(hunt.time.temporal.TemporalField, int, int, int) 200 */ 201 override bool isFixedWidth(DateTimeParseContext context) 202 { 203 if (context.isStrict() == false) 204 { 205 return false; 206 } 207 return super.isFixedWidth(context); 208 } 209 210 override public string toString() 211 { 212 return "ReducedValue(" ~ typeid(field) 213 .name ~ "," ~ minWidth.to!string ~ "," ~ maxWidth.to!string ~ "," ~ typeid(baseDate) 214 .name ~ ")"; 215 } 216 }