1 module hunt.time.format.InstantPrinterParser; 2 3 import hunt.time.ZoneOffset; 4 import hunt.time.LocalDateTime; 5 import hunt.time.format.CompositePrinterParser; 6 import hunt.time.format.DateTimeParseContext; 7 import hunt.time.format.DateTimePrinterParser; 8 import hunt.time.format.DateTimePrintContext; 9 import hunt.time.temporal.ChronoField; 10 import hunt.time.temporal.TemporalField; 11 import hunt.util.StringBuilder; 12 13 import hunt.Exceptions; 14 import hunt.Long; 15 import hunt.math.Helper; 16 import hunt.util.Common; 17 18 import std.conv; 19 20 //----------------------------------------------------------------------- 21 /** 22 * Prints or parses an ISO-8601 instant. 23 */ 24 static final class InstantPrinterParser : DateTimePrinterParser 25 { 26 // days _in a 400 year cycle = 146097 27 // days _in a 10,000 year cycle = 146097 * 25 28 // seconds per day = 86400 29 private enum long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L; 30 private enum long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L; 31 private int fractionalDigits; 32 33 this(int fractionalDigits) 34 { 35 this.fractionalDigits = fractionalDigits; 36 } 37 38 override public bool format(DateTimePrintContext context, StringBuilder buf) 39 { 40 // use INSTANT_SECONDS, thus this code is not bound by Instant.MAX 41 Long inSecs = context.getValue(ChronoField.INSTANT_SECONDS); 42 Long inNanos = null; 43 if (context.getTemporal().isSupported(ChronoField.NANO_OF_SECOND)) 44 { 45 inNanos = new Long(context.getTemporal().getLong(ChronoField.NANO_OF_SECOND)); 46 } 47 if (inSecs is null) 48 { 49 return false; 50 } 51 long inSec = inSecs.longValue(); 52 int inNano = ChronoField.NANO_OF_SECOND.checkValidIntValue(inNanos !is null 53 ? inNanos.longValue() : 0); 54 // format mostly using LocalDateTime.toString 55 if (inSec >= -SECONDS_0000_TO_1970) 56 { 57 // current era 58 long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970; 59 long hi = MathHelper.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1; 60 long lo = MathHelper.floorMod(zeroSecs, SECONDS_PER_10000_YEARS); 61 LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 62 0, ZoneOffset.UTC); 63 if (hi > 0) 64 { 65 buf.append('+').append(hi); 66 } 67 buf.append(ldt.toString); 68 if (ldt.getSecond() == 0) 69 { 70 buf.append(":00"); 71 } 72 } 73 else 74 { 75 // before current era 76 long zeroSecs = inSec + SECONDS_0000_TO_1970; 77 long hi = zeroSecs / SECONDS_PER_10000_YEARS; 78 long lo = zeroSecs % SECONDS_PER_10000_YEARS; 79 LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 80 0, ZoneOffset.UTC); 81 int pos = buf.length(); 82 buf.append(ldt.toString); 83 if (ldt.getSecond() == 0) 84 { 85 buf.append(":00"); 86 } 87 if (hi < 0) 88 { 89 if (ldt.getYear() == -10_000) 90 { 91 buf.replace(pos, pos + 2, to!string(hi - 1)); 92 } 93 else if (lo == 0) 94 { 95 buf.insert(pos, hi); 96 } 97 else 98 { 99 buf.insert(pos + 1, (MathHelper.abs(hi))); 100 } 101 } 102 } 103 // add fraction 104 if ((fractionalDigits < 0 && inNano > 0) || fractionalDigits > 0) 105 { 106 buf.append('.'); 107 int div = 100_000_000; 108 for (int i = 0; ((fractionalDigits == -1 && inNano > 0) 109 || (fractionalDigits == -2 && (inNano > 0 || (i % 3) != 0)) 110 || i < fractionalDigits); i++) 111 { 112 int digit = inNano / div; 113 buf.append( /* cast(char) */ (digit.to!string ~ '0')); 114 inNano = inNano - (digit * div); 115 div = div / 10; 116 } 117 } 118 buf.append('Z'); 119 return true; 120 } 121 122 override public int parse(DateTimeParseContext context, string text, int position) 123 { 124 implementationMissing(false); 125 return 0; 126 // new context to avoid overwriting fields like year/month/day 127 // int minDigits = (fractionalDigits < 0 ? 0 : fractionalDigits); 128 // int maxDigits = (fractionalDigits < 0 ? 9 : fractionalDigits); 129 // CompositePrinterParser parser = new DateTimeFormatterBuilder().append( 130 // DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T') 131 // .appendValue(ChronoField.HOUR_OF_DAY, 132 // 2).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral(':') 133 // .appendValue(ChronoField.SECOND_OF_MINUTE, 2).appendFraction( 134 // ChronoField.NANO_OF_SECOND, minDigits, maxDigits, 135 // true).appendOffsetId().toFormatter().toPrinterParser(false); 136 // DateTimeParseContext newContext = context.copy(); 137 // int pos = parser.parse(newContext, text, position); 138 // if (pos < 0) 139 // { 140 // return pos; 141 // } 142 // // parser restricts most fields to 2 digits, so definitely int 143 // // correctly parsed nano is also guaranteed to be valid 144 // long yearParsed = newContext.getParsed(ChronoField.YEAR).longValue(); 145 // int month = newContext.getParsed(ChronoField.MONTH_OF_YEAR).intValue(); 146 // int day = newContext.getParsed(ChronoField.DAY_OF_MONTH).intValue(); 147 // int hour = newContext.getParsed(ChronoField.HOUR_OF_DAY).intValue(); 148 // int min = newContext.getParsed(ChronoField.MINUTE_OF_HOUR).intValue(); 149 // Long secVal = newContext.getParsed(ChronoField.SECOND_OF_MINUTE); 150 // Long nanoVal = newContext.getParsed(ChronoField.NANO_OF_SECOND); 151 // int sec = (secVal !is null ? secVal.intValue() : 0); 152 // int nano = (nanoVal !is null ? nanoVal.intValue() : 0); 153 // int offset = newContext.getParsed(ChronoField.OFFSET_SECONDS).intValue(); 154 // int days = 0; 155 // if (hour == 24 && min == 0 && sec == 0 && nano == 0) 156 // { 157 // hour = 0; 158 // days = 1; 159 // } 160 // else if (hour == 23 && min == 59 && sec == 60) 161 // { 162 // context.setParsedLeapSecond(); 163 // sec = 59; 164 // } 165 // int year = cast(int) yearParsed % 10_000; 166 // long instantSecs; 167 // try 168 // { 169 // LocalDateTime ldt = LocalDateTime.of(year, month, day, 170 // hour, min, sec, 0).plusDays(days); 171 // instantSecs = ldt.toEpochSecond(ZoneOffset.ofTotalSeconds(offset)); 172 // instantSecs += MathHelper.multiplyExact(yearParsed / 10_000L, SECONDS_PER_10000_YEARS); 173 // } 174 // catch (RuntimeException ex) 175 // { 176 // return ~position; 177 // } 178 // int successPos = pos; 179 // successPos = context.setParsedField(ChronoField.INSTANT_SECONDS, 180 // instantSecs, position, successPos); 181 // return context.setParsedField(ChronoField.NANO_OF_SECOND, nano, 182 // position, successPos); 183 } 184 185 override public string toString() 186 { 187 return "Instant()"; 188 } 189 }