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 }