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.format.DateTimeFormatterBuilder; 13 14 import hunt.time.format.NumberPrinterParser; 15 import hunt.time.format.OffsetIdPrinterParser; 16 17 import hunt.time.chrono.ChronoLocalDate; 18 import hunt.time.chrono.ChronoLocalDateTime; 19 import hunt.time.chrono.Chronology; 20 import hunt.time.chrono.IsoChronology; 21 22 import hunt.time.format.CharLiteralPrinterParser; 23 import hunt.time.format.ChronoPrinterParser; 24 import hunt.time.format.CompositePrinterParser; 25 import hunt.time.format.DateTimeFormatter; 26 import hunt.time.format.DateTimeParseContext; 27 import hunt.time.format.DateTimePrintContext; 28 import hunt.time.format.DateTimePrinterParser; 29 import hunt.time.format.DateTimeTextProvider; 30 import hunt.time.format.DecimalStyle; 31 import hunt.time.format.InstantPrinterParser; 32 import hunt.time.format.FractionPrinterParser; 33 import hunt.time.format.FormatStyle; 34 import hunt.time.format.LocalizedOffsetIdPrinterParser; 35 import hunt.time.format.LocalizedPrinterParser; 36 import hunt.time.format.NumberPrinterParser; 37 import hunt.time.format.PadPrinterParserDecorator; 38 import hunt.time.format.ReducedPrinterParser; 39 import hunt.time.format.ResolverStyle; 40 import hunt.time.format.SettingsParser; 41 import hunt.time.format.SignStyle; 42 import hunt.time.format.StringLiteralPrinterParser; 43 import hunt.time.format.TextPrinterParser; 44 import hunt.time.format.TextStyle; 45 import hunt.time.format.WeekBasedFieldPrinterParser; 46 import hunt.time.format.ZoneIdPrinterParser; 47 import hunt.time.format.ZoneTextPrinterParser; 48 49 import hunt.time.temporal.ChronoField; 50 import hunt.time.temporal.TemporalAccessor; 51 import hunt.time.temporal.TemporalField; 52 import hunt.time.temporal.TemporalQuery; 53 import hunt.time.temporal.TemporalQueries; 54 55 import hunt.time.util.Common; 56 import hunt.time.util.QueryHelper; 57 import hunt.time.ZoneId; 58 import hunt.time.ZoneOffset; 59 60 import hunt.collection.ArrayList; 61 import hunt.collection.HashMap; 62 import hunt.collection.Iterator; 63 import hunt.collection.List; 64 import hunt.collection.LinkedHashMap; 65 import hunt.collection.Map; 66 import hunt.collection.Set; 67 import hunt.Exceptions; 68 import hunt.Long; 69 import hunt.text.Common; 70 import hunt.util.StringBuilder; 71 import hunt.util.Common; 72 import hunt.util.Comparator; 73 import hunt.util.Locale; 74 75 import std.conv; 76 import std.concurrency : initOnce; 77 import hunt.time.temporal.IsoFields; 78 79 80 /** 81 * Builder to create date-time formatters. 82 * !(p) 83 * This allows a {@code DateTimeFormatter} to be created. 84 * All date-time formatters are created ultimately using this builder. 85 * !(p) 86 * The basic elements of date-time can all be added: 87 * !(ul) 88 * !(li)Value - a numeric value</li> 89 * !(li)Fraction - a fractional value including the decimal place. Always use this when 90 * outputting fractions to ensure that the fraction is parsed correctly</li> 91 * !(li)Text - the textual equivalent for the value</li> 92 * !(li)OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li> 93 * !(li)ZoneId - the {@linkplain ZoneId time-zone} id</li> 94 * !(li)ZoneText - the name of the time-zone</li> 95 * !(li)ChronologyId - the {@linkplain Chronology chronology} id</li> 96 * !(li)ChronologyText - the name of the chronology</li> 97 * !(li)Literal - a text literal</li> 98 * !(li)Nested and Optional - formats can be nested or made optional</li> 99 * </ul> 100 * In addition, any of the elements may be decorated by padding, either with spaces or any other character. 101 * !(p) 102 * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat} 103 * can be used, see {@link #appendPattern(string)}. 104 * In practice, this simply parses the pattern and calls other methods on the builder. 105 * 106 * @implSpec 107 * This class is a mutable builder intended for use from a single thread. 108 * 109 * @since 1.8 110 */ 111 public final class DateTimeFormatterBuilder { 112 113 // dfmt off 114 //----------------------------------------------------------------------- 115 /** 116 * The ISO date formatter that formats or parses a date without an 117 * offset, such as '2011-12-03'. 118 * !(p) 119 * This returns an immutable formatter capable of formatting and parsing 120 * the ISO-8601 extended local date format. 121 * The format consists of: 122 * !(ul) 123 * !(li)Four digits or more for the {@link ChronoField#YEAR year}. 124 * Years _in the range 0000 to 9999 will be pre-padded by zero to ensure four digits. 125 * Years outside that range will have a prefixed positive or negative symbol. 126 * !(li)A dash 127 * !(li)Two digits for the {@link ChronoField#MONTH_OF_YEAR month-of-year}. 128 * This is pre-padded by zero to ensure two digits. 129 * !(li)A dash 130 * !(li)Two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}. 131 * This is pre-padded by zero to ensure two digits. 132 * </ul> 133 * !(p) 134 * The returned formatter has a chronology of ISO set to ensure dates _in 135 * other calendar systems are correctly converted. 136 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 137 */ 138 static DateTimeFormatter ISO_LOCAL_DATE() { 139 __gshared DateTimeFormatter _ISO_LOCAL_DATE ; 140 return initOnce!(_ISO_LOCAL_DATE)({ 141 return new DateTimeFormatterBuilder() 142 .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD) 143 .appendLiteral('-') 144 .appendValue(ChronoField.MONTH_OF_YEAR, 2) 145 .appendLiteral('-') 146 .appendValue(ChronoField.DAY_OF_MONTH, 2) 147 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 148 }()); 149 } 150 151 //----------------------------------------------------------------------- 152 /** 153 * The ISO date formatter that formats or parses a date with an 154 * offset, such as '2011-12-03+01:00'. 155 * !(p) 156 * This returns an immutable formatter capable of formatting and parsing 157 * the ISO-8601 extended offset date format. 158 * The format consists of: 159 * !(ul) 160 * !(li)The {@link #ISO_LOCAL_DATE} 161 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 162 * they will be handled even though this is not part of the ISO-8601 standard. 163 * Parsing is case insensitive. 164 * </ul> 165 * !(p) 166 * The returned formatter has a chronology of ISO set to ensure dates _in 167 * other calendar systems are correctly converted. 168 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 169 */ 170 static DateTimeFormatter ISO_OFFSET_DATE() { 171 __gshared DateTimeFormatter _ISO_OFFSET_DATE ; 172 return initOnce!(_ISO_OFFSET_DATE)({ 173 return new DateTimeFormatterBuilder() 174 .parseCaseInsensitive() 175 .append(ISO_LOCAL_DATE()) 176 .appendOffsetId() 177 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 178 }()); 179 } 180 181 //----------------------------------------------------------------------- 182 /** 183 * The ISO date formatter that formats or parses a date with the 184 * offset if available, such as '2011-12-03' or '2011-12-03+01:00'. 185 * !(p) 186 * This returns an immutable formatter capable of formatting and parsing 187 * the ISO-8601 extended date format. 188 * The format consists of: 189 * !(ul) 190 * !(li)The {@link #ISO_LOCAL_DATE} 191 * !(li)If the offset is not available then the format is complete. 192 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 193 * they will be handled even though this is not part of the ISO-8601 standard. 194 * Parsing is case insensitive. 195 * </ul> 196 * !(p) 197 * As this formatter has an optional element, it may be necessary to parse using 198 * {@link DateTimeFormatter#parseBest}. 199 * !(p) 200 * The returned formatter has a chronology of ISO set to ensure dates _in 201 * other calendar systems are correctly converted. 202 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 203 */ 204 static DateTimeFormatter ISO_DATE() { 205 __gshared DateTimeFormatter _ISO_DATE ; 206 return initOnce!(_ISO_DATE)({ 207 return new DateTimeFormatterBuilder() 208 .parseCaseInsensitive() 209 .append(ISO_LOCAL_DATE()) 210 .optionalStart() 211 .appendOffsetId() 212 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 213 }()); 214 } 215 216 217 //----------------------------------------------------------------------- 218 /** 219 * The ISO time formatter that formats or parses a time without an 220 * offset, such as '10:15' or '10:15:30'. 221 * !(p) 222 * This returns an immutable formatter capable of formatting and parsing 223 * the ISO-8601 extended local time format. 224 * The format consists of: 225 * !(ul) 226 * !(li)Two digits for the {@link ChronoField#HOUR_OF_DAY hour-of-day}. 227 * This is pre-padded by zero to ensure two digits. 228 * !(li)A colon 229 * !(li)Two digits for the {@link ChronoField#MINUTE_OF_HOUR minute-of-hour}. 230 * This is pre-padded by zero to ensure two digits. 231 * !(li)If the second-of-minute is not available then the format is complete. 232 * !(li)A colon 233 * !(li)Two digits for the {@link ChronoField#SECOND_OF_MINUTE second-of-minute}. 234 * This is pre-padded by zero to ensure two digits. 235 * !(li)If the nano-of-second is zero or not available then the format is complete. 236 * !(li)A decimal point 237 * !(li)One to nine digits for the {@link ChronoField#NANO_OF_SECOND nano-of-second}. 238 * As many digits will be output as required. 239 * </ul> 240 * !(p) 241 * The returned formatter has no override chronology or zone. 242 * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. 243 */ 244 static DateTimeFormatter ISO_LOCAL_TIME() { 245 __gshared DateTimeFormatter _ISO_LOCAL_TIME ; 246 return initOnce!(_ISO_LOCAL_TIME)({ 247 return new DateTimeFormatterBuilder() 248 .appendValue(ChronoField.HOUR_OF_DAY, 2) 249 .appendLiteral(':') 250 .appendValue(ChronoField.MINUTE_OF_HOUR, 2) 251 .optionalStart() 252 .appendLiteral(':') 253 .appendValue(ChronoField.SECOND_OF_MINUTE, 2) 254 .optionalStart() 255 .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) 256 .toFormatter(ResolverStyle.STRICT, null); 257 }()); 258 } 259 260 //----------------------------------------------------------------------- 261 /** 262 * The ISO time formatter that formats or parses a time with an 263 * offset, such as '10:15+01:00' or '10:15:30+01:00'. 264 * !(p) 265 * This returns an immutable formatter capable of formatting and parsing 266 * the ISO-8601 extended offset time format. 267 * The format consists of: 268 * !(ul) 269 * !(li)The {@link #ISO_LOCAL_TIME} 270 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 271 * they will be handled even though this is not part of the ISO-8601 standard. 272 * Parsing is case insensitive. 273 * </ul> 274 * !(p) 275 * The returned formatter has no override chronology or zone. 276 * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. 277 */ 278 static DateTimeFormatter ISO_OFFSET_TIME() { 279 __gshared DateTimeFormatter _ISO_OFFSET_TIME ; 280 return initOnce!(_ISO_OFFSET_TIME)({ 281 return new DateTimeFormatterBuilder() 282 .parseCaseInsensitive() 283 .append(ISO_LOCAL_TIME()) 284 .appendOffsetId() 285 .toFormatter(ResolverStyle.STRICT, null); 286 }()); 287 } 288 289 290 //----------------------------------------------------------------------- 291 /** 292 * The ISO time formatter that formats or parses a time, with the 293 * offset if available, such as '10:15', '10:15:30' or '10:15:30+01:00'. 294 * !(p) 295 * This returns an immutable formatter capable of formatting and parsing 296 * the ISO-8601 extended offset time format. 297 * The format consists of: 298 * !(ul) 299 * !(li)The {@link #ISO_LOCAL_TIME} 300 * !(li)If the offset is not available then the format is complete. 301 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 302 * they will be handled even though this is not part of the ISO-8601 standard. 303 * Parsing is case insensitive. 304 * </ul> 305 * !(p) 306 * As this formatter has an optional element, it may be necessary to parse using 307 * {@link DateTimeFormatter#parseBest}. 308 * !(p) 309 * The returned formatter has no override chronology or zone. 310 * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. 311 */ 312 static DateTimeFormatter ISO_TIME() { 313 __gshared DateTimeFormatter _ISO_TIME ; 314 return initOnce!(_ISO_TIME)({ 315 return new DateTimeFormatterBuilder() 316 .parseCaseInsensitive() 317 .append(ISO_LOCAL_TIME()) 318 .optionalStart() 319 .appendOffsetId() 320 .toFormatter(ResolverStyle.STRICT, null); 321 }()); 322 } 323 324 //----------------------------------------------------------------------- 325 /** 326 * The ISO date-time formatter that formats or parses a date-time without 327 * an offset, such as '2011-12-03T10:15:30'. 328 * !(p) 329 * This returns an immutable formatter capable of formatting and parsing 330 * the ISO-8601 extended offset date-time format. 331 * The format consists of: 332 * !(ul) 333 * !(li)The {@link #ISO_LOCAL_DATE} 334 * !(li)The letter 'T'. Parsing is case insensitive. 335 * !(li)The {@link #ISO_LOCAL_TIME} 336 * </ul> 337 * !(p) 338 * The returned formatter has a chronology of ISO set to ensure dates _in 339 * other calendar systems are correctly converted. 340 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 341 */ 342 static DateTimeFormatter ISO_LOCAL_DATE_TIME() { 343 __gshared DateTimeFormatter _ISO_LOCAL_DATE_TIME ; 344 return initOnce!(_ISO_LOCAL_DATE_TIME)({ 345 return new DateTimeFormatterBuilder() 346 .parseCaseInsensitive() 347 .append(ISO_LOCAL_DATE()) 348 .appendLiteral('T') 349 .append(ISO_LOCAL_DATE()) 350 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 351 }()); 352 } 353 354 355 //----------------------------------------------------------------------- 356 /** 357 * The ISO date-time formatter that formats or parses a date-time with an 358 * offset, such as '2011-12-03T10:15:30+01:00'. 359 * !(p) 360 * This returns an immutable formatter capable of formatting and parsing 361 * the ISO-8601 extended offset date-time format. 362 * The format consists of: 363 * !(ul) 364 * !(li)The {@link #ISO_LOCAL_DATE_TIME} 365 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 366 * they will be handled even though this is not part of the ISO-8601 standard. 367 * The offset parsing is lenient, which allows the minutes and seconds to be optional. 368 * Parsing is case insensitive. 369 * </ul> 370 * !(p) 371 * The returned formatter has a chronology of ISO set to ensure dates _in 372 * other calendar systems are correctly converted. 373 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 374 */ 375 static DateTimeFormatter ISO_OFFSET_DATE_TIME() { 376 __gshared DateTimeFormatter _ISO_OFFSET_DATE_TIME ; 377 return initOnce!(_ISO_OFFSET_DATE_TIME)({ 378 return new DateTimeFormatterBuilder() 379 .parseCaseInsensitive() 380 .append(ISO_LOCAL_DATE_TIME()) 381 .parseLenient() 382 .appendOffsetId() 383 .parseStrict() 384 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 385 }()); 386 } 387 388 389 //----------------------------------------------------------------------- 390 /** 391 * The ISO-like date-time formatter that formats or parses a date-time with 392 * offset and zone, such as '2011-12-03T10:15:30+01:00[Europe/Paris]'. 393 * !(p) 394 * This returns an immutable formatter capable of formatting and parsing 395 * a format that extends the ISO-8601 extended offset date-time format 396 * to add the time-zone. 397 * The section _in square brackets is not part of the ISO-8601 standard. 398 * The format consists of: 399 * !(ul) 400 * !(li)The {@link #ISO_OFFSET_DATE_TIME} 401 * !(li)If the zone ID is not available or is a {@code ZoneOffset} then the format is complete. 402 * !(li)An open square bracket '['. 403 * !(li)The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard. 404 * Parsing is case sensitive. 405 * !(li)A close square bracket ']'. 406 * </ul> 407 * !(p) 408 * The returned formatter has a chronology of ISO set to ensure dates _in 409 * other calendar systems are correctly converted. 410 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 411 */ 412 static DateTimeFormatter ISO_ZONED_DATE_TIME() { 413 __gshared DateTimeFormatter _ISO_ZONED_DATE_TIME ; 414 return initOnce!(_ISO_ZONED_DATE_TIME)({ 415 return new DateTimeFormatterBuilder() 416 .append(ISO_OFFSET_DATE_TIME()) 417 .optionalStart() 418 .appendLiteral('[') 419 .parseCaseSensitive() 420 .appendZoneRegionId() 421 .appendLiteral(']') 422 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 423 }()); 424 } 425 426 //----------------------------------------------------------------------- 427 /** 428 * The ISO-like date-time formatter that formats or parses a date-time with 429 * the offset and zone if available, such as '2011-12-03T10:15:30', 430 * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'. 431 * !(p) 432 * This returns an immutable formatter capable of formatting and parsing 433 * the ISO-8601 extended local or offset date-time format, as well as the 434 * extended non-ISO form specifying the time-zone. 435 * The format consists of: 436 * !(ul) 437 * !(li)The {@link #ISO_LOCAL_DATE_TIME} 438 * !(li)If the offset is not available to format or parse then the format is complete. 439 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 440 * they will be handled even though this is not part of the ISO-8601 standard. 441 * !(li)If the zone ID is not available or is a {@code ZoneOffset} then the format is complete. 442 * !(li)An open square bracket '['. 443 * !(li)The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard. 444 * Parsing is case sensitive. 445 * !(li)A close square bracket ']'. 446 * </ul> 447 * !(p) 448 * As this formatter has an optional element, it may be necessary to parse using 449 * {@link DateTimeFormatter#parseBest}. 450 * !(p) 451 * The returned formatter has a chronology of ISO set to ensure dates _in 452 * other calendar systems are correctly converted. 453 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 454 */ 455 static DateTimeFormatter ISO_DATE_TIME() { 456 __gshared DateTimeFormatter _ISO_DATE_TIME ; 457 return initOnce!(_ISO_DATE_TIME)({ 458 return new DateTimeFormatterBuilder() 459 .append(ISO_LOCAL_DATE_TIME()) 460 .optionalStart() 461 .appendOffsetId() 462 .optionalStart() 463 .appendLiteral('[') 464 .parseCaseSensitive() 465 .appendZoneRegionId() 466 .appendLiteral(']') 467 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 468 }()); 469 } 470 471 472 //----------------------------------------------------------------------- 473 /** 474 * The ISO date formatter that formats or parses the ordinal date 475 * without an offset, such as '2012-337'. 476 * !(p) 477 * This returns an immutable formatter capable of formatting and parsing 478 * the ISO-8601 extended ordinal date format. 479 * The format consists of: 480 * !(ul) 481 * !(li)Four digits or more for the {@link ChronoField#YEAR year}. 482 * Years _in the range 0000 to 9999 will be pre-padded by zero to ensure four digits. 483 * Years outside that range will have a prefixed positive or negative symbol. 484 * !(li)A dash 485 * !(li)Three digits for the {@link ChronoField#DAY_OF_YEAR day-of-year}. 486 * This is pre-padded by zero to ensure three digits. 487 * !(li)If the offset is not available to format or parse then the format is complete. 488 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 489 * they will be handled even though this is not part of the ISO-8601 standard. 490 * Parsing is case insensitive. 491 * </ul> 492 * !(p) 493 * As this formatter has an optional element, it may be necessary to parse using 494 * {@link DateTimeFormatter#parseBest}. 495 * !(p) 496 * The returned formatter has a chronology of ISO set to ensure dates _in 497 * other calendar systems are correctly converted. 498 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 499 */ 500 static DateTimeFormatter ISO_ORDINAL_DATE() { 501 __gshared DateTimeFormatter _ISO_ORDINAL_DATE ; 502 return initOnce!(_ISO_ORDINAL_DATE)({ 503 return new DateTimeFormatterBuilder() 504 .parseCaseInsensitive() 505 .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD) 506 .appendLiteral('-') 507 .appendValue(ChronoField.DAY_OF_YEAR, 3) 508 .optionalStart() 509 .appendOffsetId() 510 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 511 }()); 512 } 513 514 //----------------------------------------------------------------------- 515 /** 516 * The ISO date formatter that formats or parses the week-based date 517 * without an offset, such as '2012-W48-6'. 518 * !(p) 519 * This returns an immutable formatter capable of formatting and parsing 520 * the ISO-8601 extended week-based date format. 521 * The format consists of: 522 * !(ul) 523 * !(li)Four digits or more for the {@link IsoFields#WEEK_BASED_YEAR week-based-year}. 524 * Years _in the range 0000 to 9999 will be pre-padded by zero to ensure four digits. 525 * Years outside that range will have a prefixed positive or negative symbol. 526 * !(li)A dash 527 * !(li)The letter 'W'. Parsing is case insensitive. 528 * !(li)Two digits for the {@link IsoFields#WEEK_OF_WEEK_BASED_YEAR week-of-week-based-year}. 529 * This is pre-padded by zero to ensure three digits. 530 * !(li)A dash 531 * !(li)One digit for the {@link ChronoField#DAY_OF_WEEK day-of-week}. 532 * The value run from Monday (1) to Sunday (7). 533 * !(li)If the offset is not available to format or parse then the format is complete. 534 * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then 535 * they will be handled even though this is not part of the ISO-8601 standard. 536 * Parsing is case insensitive. 537 * </ul> 538 * !(p) 539 * As this formatter has an optional element, it may be necessary to parse using 540 * {@link DateTimeFormatter#parseBest}. 541 * !(p) 542 * The returned formatter has a chronology of ISO set to ensure dates _in 543 * other calendar systems are correctly converted. 544 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 545 */ 546 static DateTimeFormatter ISO_WEEK_DATE() { 547 __gshared DateTimeFormatter _ISO_WEEK_DATE ; 548 return initOnce!(_ISO_WEEK_DATE)({ 549 return new DateTimeFormatterBuilder() 550 .parseCaseInsensitive() 551 .appendValue(IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD) 552 .appendLiteral("-W") 553 .appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2) 554 .appendLiteral('-') 555 .appendValue(ChronoField.DAY_OF_WEEK, 1) 556 .optionalStart() 557 .appendOffsetId() 558 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 559 }()); 560 } 561 562 563 //----------------------------------------------------------------------- 564 /** 565 * The ISO instant formatter that formats or parses an instant _in UTC, 566 * such as '2011-12-03T10:15:30Z'. 567 * !(p) 568 * This returns an immutable formatter capable of formatting and parsing 569 * the ISO-8601 instant format. 570 * When formatting, the instant will always be suffixed by 'Z' to indicate UTC. 571 * The second-of-minute is always output. 572 * The nano-of-second outputs zero, three, six or nine digits as necessary. 573 * When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()} 574 * will be used to parse the offset, converting the instant to UTC as necessary. 575 * The time to at least the seconds field is required. 576 * Fractional seconds from zero to nine are parsed. 577 * The localized decimal style is not used. 578 * !(p) 579 * This is a special case formatter intended to allow a human readable form 580 * of an {@link hunt.time.Instant}. The {@code Instant} class is designed to 581 * only represent a point _in time and internally stores a value _in nanoseconds 582 * from a fixed epoch of 1970-01-01Z. As such, an {@code Instant} cannot be 583 * formatted as a date or time without providing some form of time-zone. 584 * This formatter allows the {@code Instant} to be formatted, by providing 585 * a suitable conversion using {@code ZoneOffset.UTC}. 586 * !(p) 587 * The format consists of: 588 * !(ul) 589 * !(li)The {@link #ISO_OFFSET_DATE_TIME} where the instant is converted from 590 * {@link ChronoField#INSTANT_SECONDS} and {@link ChronoField#NANO_OF_SECOND} 591 * using the {@code UTC} offset. Parsing is case insensitive. 592 * </ul> 593 * !(p) 594 * The returned formatter has no override chronology or zone. 595 * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. 596 */ 597 static DateTimeFormatter ISO_INSTANT() { 598 __gshared DateTimeFormatter _ISO_INSTANT ; 599 return initOnce!(_ISO_INSTANT)({ 600 return new DateTimeFormatterBuilder() 601 .parseCaseInsensitive() 602 .appendInstant() 603 .toFormatter(ResolverStyle.STRICT, null); 604 }()); 605 } 606 607 //----------------------------------------------------------------------- 608 /** 609 * The ISO date formatter that formats or parses a date without an 610 * offset, such as '20111203'. 611 * !(p) 612 * This returns an immutable formatter capable of formatting and parsing 613 * the ISO-8601 basic local date format. 614 * The format consists of: 615 * !(ul) 616 * !(li)Four digits for the {@link ChronoField#YEAR year}. 617 * Only years _in the range 0000 to 9999 are supported. 618 * !(li)Two digits for the {@link ChronoField#MONTH_OF_YEAR month-of-year}. 619 * This is pre-padded by zero to ensure two digits. 620 * !(li)Two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}. 621 * This is pre-padded by zero to ensure two digits. 622 * !(li)If the offset is not available to format or parse then the format is complete. 623 * !(li)The {@link ZoneOffset#getId() offset ID} without colons. If the offset has 624 * seconds then they will be handled even though this is not part of the ISO-8601 standard. 625 * The offset parsing is lenient, which allows the minutes and seconds to be optional. 626 * Parsing is case insensitive. 627 * </ul> 628 * !(p) 629 * As this formatter has an optional element, it may be necessary to parse using 630 * {@link DateTimeFormatter#parseBest}. 631 * !(p) 632 * The returned formatter has a chronology of ISO set to ensure dates _in 633 * other calendar systems are correctly converted. 634 * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. 635 */ 636 static DateTimeFormatter BASIC_ISO_DATE() { 637 __gshared DateTimeFormatter _BASIC_ISO_DATE ; 638 return initOnce!(_BASIC_ISO_DATE)({ 639 return new DateTimeFormatterBuilder() 640 .parseCaseInsensitive() 641 .appendValue(ChronoField.YEAR, 4) 642 .appendValue(ChronoField.MONTH_OF_YEAR, 2) 643 .appendValue(ChronoField.DAY_OF_MONTH, 2) 644 .optionalStart() 645 .parseLenient() 646 .appendOffset("+HHMMss", "Z") 647 .parseStrict() 648 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); 649 }()); 650 } 651 652 //----------------------------------------------------------------------- 653 /** 654 * The RFC-1123 date-time formatter, such as 'Tue, 3 Jun 2008 11:05:30 GMT'. 655 * !(p) 656 * This returns an immutable formatter capable of formatting and parsing 657 * most of the RFC-1123 format. 658 * RFC-1123 updates RFC-822 changing the year from two digits to four. 659 * This implementation requires a four digit year. 660 * This implementation also does not handle North American or military zone 661 * names, only 'GMT' and offset amounts. 662 * !(p) 663 * The format consists of: 664 * !(ul) 665 * !(li)If the day-of-week is not available to format or parse then jump to day-of-month. 666 * !(li)Three letter {@link ChronoField#DAY_OF_WEEK day-of-week} _in English. 667 * !(li)A comma 668 * !(li)A space 669 * !(li)One or two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}. 670 * !(li)A space 671 * !(li)Three letter {@link ChronoField#MONTH_OF_YEAR month-of-year} _in English. 672 * !(li)A space 673 * !(li)Four digits for the {@link ChronoField#YEAR year}. 674 * Only years _in the range 0000 to 9999 are supported. 675 * !(li)A space 676 * !(li)Two digits for the {@link ChronoField#HOUR_OF_DAY hour-of-day}. 677 * This is pre-padded by zero to ensure two digits. 678 * !(li)A colon 679 * !(li)Two digits for the {@link ChronoField#MINUTE_OF_HOUR minute-of-hour}. 680 * This is pre-padded by zero to ensure two digits. 681 * !(li)If the second-of-minute is not available then jump to the next space. 682 * !(li)A colon 683 * !(li)Two digits for the {@link ChronoField#SECOND_OF_MINUTE second-of-minute}. 684 * This is pre-padded by zero to ensure two digits. 685 * !(li)A space 686 * !(li)The {@link ZoneOffset#getId() offset ID} without colons or seconds. 687 * An offset of zero uses "GMT". North American zone names and military zone names are not handled. 688 * </ul> 689 * !(p) 690 * Parsing is case insensitive. 691 * !(p) 692 * The returned formatter has a chronology of ISO set to ensure dates _in 693 * other calendar systems are correctly converted. 694 * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. 695 */ 696 static DateTimeFormatter RFC_1123_DATE_TIME() { 697 __gshared DateTimeFormatter _RFC_1123_DATE_TIME ; 698 return initOnce!(_RFC_1123_DATE_TIME)({ 699 // manually code maps to ensure correct data always used 700 // (locale data can be changed by application code) 701 Map!(Long, string) dow = new HashMap!(Long, string)(); 702 dow.put(new Long(1L), "Mon"); 703 dow.put(new Long(2L), "Tue"); 704 dow.put(new Long(3L), "Wed"); 705 dow.put(new Long(4L), "Thu"); 706 dow.put(new Long(5L), "Fri"); 707 dow.put(new Long(6L), "Sat"); 708 dow.put(new Long(7L), "Sun"); 709 Map!(Long, string) moy = new HashMap!(Long, string)(); 710 moy.put(new Long(1L), "Jan"); 711 moy.put(new Long(2L), "Feb"); 712 moy.put(new Long(3L), "Mar"); 713 moy.put(new Long(4L), "Apr"); 714 moy.put(new Long(5L), "May"); 715 moy.put(new Long(6L), "Jun"); 716 moy.put(new Long(7L), "Jul"); 717 moy.put(new Long(8L), "Aug"); 718 moy.put(new Long(9L), "Sep"); 719 moy.put(new Long(10L), "Oct"); 720 moy.put(new Long(11L), "Nov"); 721 moy.put(new Long(12L), "Dec"); 722 723 return new DateTimeFormatterBuilder() 724 .parseCaseInsensitive() 725 .parseLenient() 726 .optionalStart() 727 .appendText(ChronoField.DAY_OF_WEEK, dow) 728 .appendLiteral(", ") 729 .optionalEnd() 730 .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) 731 .appendLiteral(' ') 732 .appendText(ChronoField.MONTH_OF_YEAR, moy) 733 .appendLiteral(' ') 734 .appendValue(ChronoField.YEAR, 4) // 2 digit year not handled 735 .appendLiteral(' ') 736 .appendValue(ChronoField.HOUR_OF_DAY, 2) 737 .appendLiteral(':') 738 .appendValue(ChronoField.MINUTE_OF_HOUR, 2) 739 .optionalStart() 740 .appendLiteral(':') 741 .appendValue(ChronoField.SECOND_OF_MINUTE, 2) 742 .optionalEnd() 743 .appendLiteral(' ') 744 .appendOffset("+HHMM", "GMT") // should handle UT/Z/EST/EDT/CST/CDT/MST/MDT/PST/MDT 745 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); 746 }()); 747 } 748 749 /** 750 * Query for a time-zone that is region-only. 751 */ 752 static TemporalQuery!(ZoneId) QUERY_REGION_ONLY() { 753 __gshared TemporalQuery!(ZoneId) _QUERY_REGION_ONLY; 754 return initOnce!(_QUERY_REGION_ONLY)({ 755 return new class TemporalQuery!(ZoneId) { 756 ZoneId queryFrom(TemporalAccessor temporal){ 757 ZoneId zone = QueryHelper.query!ZoneId(temporal ,TemporalQueries.zoneId()); 758 return (zone !is null && (cast(ZoneOffset)(zone) !is null) == false ? zone : null); 759 } 760 }; 761 }()); 762 } 763 764 // dfmt on 765 766 /** 767 * The currently active builder, used by the outermost builder. 768 */ 769 private DateTimeFormatterBuilder _active; 770 // alias active = this; 771 772 DateTimeFormatterBuilder active() @trusted nothrow 773 { 774 if (_active is null) 775 return this; 776 else 777 return _active; 778 } 779 /** 780 * The parent builder, null for the outermost builder. 781 */ 782 private DateTimeFormatterBuilder parent; 783 /** 784 * The list of printers that will be used. 785 */ 786 private List!(DateTimePrinterParser) printerParsers; 787 /** 788 * Whether this builder produces an optional formatter. 789 */ 790 private bool optional; 791 /** 792 * The width to pad the next field to. 793 */ 794 private int padNextWidth; 795 /** 796 * The character to pad the next field with. 797 */ 798 private char padNextChar; 799 /** 800 * The index of the last variable width value parser. 801 */ 802 private int valueParserIndex = -1; 803 804 805 /** 806 * Creates a formatter using the specified pattern. 807 * !(p) 808 * This method will create a formatter based on a simple 809 * <a href="#patterns">pattern of letters and symbols</a> 810 * as described _in the class documentation. 811 * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'. 812 * !(p) 813 * The formatter will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. 814 * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter. 815 * Alternatively use the {@link #ofPattern(string, Locale)} variant of this method. 816 * !(p) 817 * The returned formatter has no override chronology or zone. 818 * It uses {@link ResolverStyle#SMART SMART} resolver style. 819 * 820 * @param pattern the pattern to use, not null 821 * @return the formatter based on the pattern, not null 822 * @throws IllegalArgumentException if the pattern is invalid 823 * @see DateTimeFormatterBuilder#appendPattern(string) 824 */ 825 public static DateTimeFormatter ofPattern(string pattern) { 826 return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(); 827 } 828 829 /** 830 * Creates a formatter using the specified pattern and locale. 831 * !(p) 832 * This method will create a formatter based on a simple 833 * <a href="#patterns">pattern of letters and symbols</a> 834 * as described _in the class documentation. 835 * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'. 836 * !(p) 837 * The formatter will use the specified locale. 838 * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter. 839 * !(p) 840 * The returned formatter has no override chronology or zone. 841 * It uses {@link ResolverStyle#SMART SMART} resolver style. 842 * 843 * @param pattern the pattern to use, not null 844 * @param locale the locale to use, not null 845 * @return the formatter based on the pattern, not null 846 * @throws IllegalArgumentException if the pattern is invalid 847 * @see DateTimeFormatterBuilder#appendPattern(string) 848 */ 849 public static DateTimeFormatter ofPattern(string pattern, Locale locale) { 850 return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale); 851 } 852 853 854 /** 855 * Returns a locale specific date-time formatter for the ISO chronology. 856 * !(p) 857 * This returns a formatter that will format or parse a date-time. 858 * The exact format pattern used varies by locale. 859 * !(p) 860 * The locale is determined from the formatter. The formatter returned directly by 861 * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. 862 * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} 863 * on the result of this method. 864 * !(p) 865 * Note that the localized pattern is looked up lazily. 866 * This {@code DateTimeFormatter} holds the style required and the locale, 867 * looking up the pattern required on demand. 868 * !(p) 869 * The returned formatter has a chronology of ISO set to ensure dates _in 870 * other calendar systems are correctly converted. 871 * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. 872 * The {@code FULL} and {@code LONG} styles typically require a time-zone. 873 * When formatting using these styles, a {@code ZoneId} must be available, 874 * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}. 875 * 876 * @param dateTimeStyle the formatter style to obtain, not null 877 * @return the date-time formatter, not null 878 */ 879 public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateTimeStyle) { 880 assert(dateTimeStyle, "dateTimeStyle"); 881 return new DateTimeFormatterBuilder().appendLocalized(dateTimeStyle, dateTimeStyle) 882 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); 883 } 884 885 /** 886 * Returns a locale specific date and time format for the ISO chronology. 887 * !(p) 888 * This returns a formatter that will format or parse a date-time. 889 * The exact format pattern used varies by locale. 890 * !(p) 891 * The locale is determined from the formatter. The formatter returned directly by 892 * this method will use the {@link Locale#getDefault() default FORMAT locale}. 893 * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} 894 * on the result of this method. 895 * !(p) 896 * Note that the localized pattern is looked up lazily. 897 * This {@code DateTimeFormatter} holds the style required and the locale, 898 * looking up the pattern required on demand. 899 * !(p) 900 * The returned formatter has a chronology of ISO set to ensure dates _in 901 * other calendar systems are correctly converted. 902 * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. 903 * The {@code FULL} and {@code LONG} styles typically require a time-zone. 904 * When formatting using these styles, a {@code ZoneId} must be available, 905 * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}. 906 * 907 * @param dateStyle the date formatter style to obtain, not null 908 * @param timeStyle the time formatter style to obtain, not null 909 * @return the date, time or date-time formatter, not null 910 */ 911 public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle) { 912 assert(dateStyle, "dateStyle"); 913 assert(timeStyle, "timeStyle"); 914 return new DateTimeFormatterBuilder().appendLocalized(dateStyle, timeStyle) 915 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); 916 } 917 918 /** 919 * Returns a locale specific time format for the ISO chronology. 920 * !(p) 921 * This returns a formatter that will format or parse a time. 922 * The exact format pattern used varies by locale. 923 * !(p) 924 * The locale is determined from the formatter. The formatter returned directly by 925 * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. 926 * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} 927 * on the result of this method. 928 * !(p) 929 * Note that the localized pattern is looked up lazily. 930 * This {@code DateTimeFormatter} holds the style required and the locale, 931 * looking up the pattern required on demand. 932 * !(p) 933 * The returned formatter has a chronology of ISO set to ensure dates _in 934 * other calendar systems are correctly converted. 935 * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. 936 * The {@code FULL} and {@code LONG} styles typically require a time-zone. 937 * When formatting using these styles, a {@code ZoneId} must be available, 938 * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}. 939 * 940 * @param timeStyle the formatter style to obtain, not null 941 * @return the time formatter, not null 942 */ 943 public static DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle) { 944 assert(timeStyle, "timeStyle"); 945 return new DateTimeFormatterBuilder().appendLocalized(null, timeStyle) 946 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); 947 } 948 949 //----------------------------------------------------------------------- 950 /** 951 * Returns a locale specific date format for the ISO chronology. 952 * !(p) 953 * This returns a formatter that will format or parse a date. 954 * The exact format pattern used varies by locale. 955 * !(p) 956 * The locale is determined from the formatter. The formatter returned directly by 957 * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. 958 * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} 959 * on the result of this method. 960 * !(p) 961 * Note that the localized pattern is looked up lazily. 962 * This {@code DateTimeFormatter} holds the style required and the locale, 963 * looking up the pattern required on demand. 964 * !(p) 965 * The returned formatter has a chronology of ISO set to ensure dates _in 966 * other calendar systems are correctly converted. 967 * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. 968 * 969 * @param dateStyle the formatter style to obtain, not null 970 * @return the date formatter, not null 971 */ 972 public static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) { 973 assert(dateStyle, "dateStyle"); 974 return new DateTimeFormatterBuilder().appendLocalized(dateStyle, null) 975 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); 976 } 977 978 /** 979 * Gets the formatting pattern for date and time styles for a locale and chronology. 980 * The locale and chronology are used to lookup the locale specific format 981 * for the requested dateStyle and/or timeStyle. 982 * !(p) 983 * If the locale contains the "rg" (region override) 984 * <a href="../../util/Locale.html#def_locale_extension">Unicode extensions</a>, 985 * the formatting pattern is overridden with the one appropriate for the region. 986 * 987 * @param dateStyle the FormatStyle for the date, null for time-only pattern 988 * @param timeStyle the FormatStyle for the time, null for date-only pattern 989 * @param chrono the Chronology, non-null 990 * @param locale the locale, non-null 991 * @return the locale and Chronology specific formatting pattern 992 * @throws IllegalArgumentException if both dateStyle and timeStyle are null 993 */ 994 // using LocalizedPrinterParser.getLocalizedDateTimePattern 995 public static string getLocalizedDateTimePattern(FormatStyle dateStyle, 996 FormatStyle timeStyle, Chronology chrono, Locale locale) 997 { 998 // 999 // assert(locale, "locale"); 1000 // assert(chrono, "chrono"); 1001 // if (dateStyle is null && timeStyle is null) { 1002 // throw new IllegalArgumentException("Either dateStyle or timeStyle must be non-null"); 1003 // } 1004 // LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, locale); 1005 // JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider(); 1006 // string pattern = provider.getJavaTimeDateTimePattern(convertStyle(timeStyle), 1007 // convertStyle(dateStyle), chrono.getCalendarType(), 1008 // CalendarDataUtility.findRegionOverride(locale)); 1009 // return pattern; 1010 implementationMissing(false); 1011 return null; 1012 } 1013 1014 /** 1015 * Converts the given FormatStyle to the java.text.DateFormat style. 1016 * 1017 * @param style the FormatStyle style 1018 * @return the int style, or -1 if style is null, indicating un-required 1019 */ 1020 private static int convertStyle(FormatStyle style) 1021 { 1022 if (style is null) 1023 { 1024 return -1; 1025 } 1026 return style.ordinal(); // indices happen to align 1027 } 1028 1029 /** 1030 * Constructs a new instance of the builder. 1031 */ 1032 public this() 1033 { 1034 // super(); 1035 printerParsers = new ArrayList!(DateTimePrinterParser)(); 1036 parent = null; 1037 optional = false; 1038 } 1039 1040 /** 1041 * Constructs a new instance of the builder. 1042 * 1043 * @param parent the parent builder, not null 1044 * @param optional whether the formatter is optional, not null 1045 */ 1046 private this(DateTimeFormatterBuilder parent, bool optional) 1047 { 1048 // super(); 1049 printerParsers = new ArrayList!(DateTimePrinterParser)(); 1050 this.parent = parent; 1051 this.optional = optional; 1052 } 1053 1054 // void opAssign(DateTimeFormatterBuilder other) 1055 // { 1056 // this.parent = other.parent; 1057 // this.optional = other.optional; 1058 // this.printerParsers = other.printerParsers; 1059 // this.padNextWidth = other.padNextWidth; 1060 // this.padNextChar = other.padNextChar; 1061 // this.valueParserIndex = other.valueParserIndex; 1062 // } 1063 1064 //----------------------------------------------------------------------- 1065 /** 1066 * Changes the parse style to be case sensitive for the remainder of the formatter. 1067 * !(p) 1068 * Parsing can be case sensitive or insensitive - by default it is case sensitive. 1069 * This method allows the case sensitivity setting of parsing to be changed. 1070 * !(p) 1071 * Calling this method changes the state of the builder such that all 1072 * subsequent builder method calls will parse text _in case sensitive mode. 1073 * See {@link #parseCaseInsensitive} for the opposite setting. 1074 * The parse case sensitive/insensitive methods may be called at any point 1075 * _in the builder, thus the parser can swap between case parsing modes 1076 * multiple times during the parse. 1077 * !(p) 1078 * Since the default is case sensitive, this method should only be used after 1079 * a previous call to {@code #parseCaseInsensitive}. 1080 * 1081 * @return this, for chaining, not null 1082 */ 1083 public DateTimeFormatterBuilder parseCaseSensitive() 1084 { 1085 appendInternal(SettingsParser.SENSITIVE); 1086 return this; 1087 } 1088 1089 /** 1090 * Changes the parse style to be case insensitive for the remainder of the formatter. 1091 * !(p) 1092 * Parsing can be case sensitive or insensitive - by default it is case sensitive. 1093 * This method allows the case sensitivity setting of parsing to be changed. 1094 * !(p) 1095 * Calling this method changes the state of the builder such that all 1096 * subsequent builder method calls will parse text _in case insensitive mode. 1097 * See {@link #parseCaseSensitive()} for the opposite setting. 1098 * The parse case sensitive/insensitive methods may be called at any point 1099 * _in the builder, thus the parser can swap between case parsing modes 1100 * multiple times during the parse. 1101 * 1102 * @return this, for chaining, not null 1103 */ 1104 public DateTimeFormatterBuilder parseCaseInsensitive() 1105 { 1106 appendInternal(SettingsParser.INSENSITIVE); 1107 return this; 1108 } 1109 1110 //----------------------------------------------------------------------- 1111 /** 1112 * Changes the parse style to be strict for the remainder of the formatter. 1113 * !(p) 1114 * Parsing can be strict or lenient - by default its strict. 1115 * This controls the degree of flexibility _in matching the text and sign styles. 1116 * !(p) 1117 * When used, this method changes the parsing to be strict from this point onwards. 1118 * As strict is the default, this is normally only needed after calling {@link #parseLenient()}. 1119 * The change will remain _in force until the end of the formatter that is eventually 1120 * constructed or until {@code parseLenient} is called. 1121 * 1122 * @return this, for chaining, not null 1123 */ 1124 public DateTimeFormatterBuilder parseStrict() 1125 { 1126 appendInternal(SettingsParser.STRICT); 1127 return this; 1128 } 1129 1130 /** 1131 * Changes the parse style to be lenient for the remainder of the formatter. 1132 * Note that case sensitivity is set separately to this method. 1133 * !(p) 1134 * Parsing can be strict or lenient - by default its strict. 1135 * This controls the degree of flexibility _in matching the text and sign styles. 1136 * Applications calling this method should typically also call {@link #parseCaseInsensitive()}. 1137 * !(p) 1138 * When used, this method changes the parsing to be lenient from this point onwards. 1139 * The change will remain _in force until the end of the formatter that is eventually 1140 * constructed or until {@code parseStrict} is called. 1141 * 1142 * @return this, for chaining, not null 1143 */ 1144 public DateTimeFormatterBuilder parseLenient() 1145 { 1146 appendInternal(SettingsParser.LENIENT); 1147 return this; 1148 } 1149 1150 //----------------------------------------------------------------------- 1151 /** 1152 * Appends a default value for a field to the formatter for use _in parsing. 1153 * !(p) 1154 * This appends an instruction to the builder to inject a default value 1155 * into the parsed result. This is especially useful _in conjunction with 1156 * optional parts of the formatter. 1157 * !(p) 1158 * For example, consider a formatter that parses the year, followed by 1159 * an optional month, with a further optional day-of-month. Using such a 1160 * formatter would require the calling code to check whether a full date, 1161 * year-month or just a year had been parsed. This method can be used to 1162 * default the month and day-of-month to a sensible value, such as the 1163 * first of the month, allowing the calling code to always get a date. 1164 * !(p) 1165 * During formatting, this method has no effect. 1166 * !(p) 1167 * During parsing, the current state of the parse is inspected. 1168 * If the specified field has no associated value, because it has not been 1169 * parsed successfully at that point, then the specified value is injected 1170 * into the parse result. Injection is immediate, thus the field-value pair 1171 * will be visible to any subsequent elements _in the formatter. 1172 * As such, this method is normally called at the end of the builder. 1173 * 1174 * @param field the field to default the value of, not null 1175 * @param value the value to default the field to 1176 * @return this, for chaining, not null 1177 */ 1178 public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) 1179 { 1180 assert(field, "field"); 1181 appendInternal(new DefaultValueParser(field, value)); 1182 return this; 1183 } 1184 1185 //----------------------------------------------------------------------- 1186 /** 1187 * Appends the value of a date-time field to the formatter using a normal 1188 * output style. 1189 * !(p) 1190 * The value of the field will be output during a format. 1191 * If the value cannot be obtained then an exception will be thrown. 1192 * !(p) 1193 * The value will be printed as per the normal format of an integer value. 1194 * Only negative numbers will be signed. No padding will be added. 1195 * !(p) 1196 * The parser for a variable width value such as this normally behaves greedily, 1197 * requiring one digit, but accepting as many digits as possible. 1198 * This behavior can be affected by 'adjacent value parsing'. 1199 * See {@link #appendValue(hunt.time.temporal.TemporalField, int)} for full details. 1200 * 1201 * @param field the field to append, not null 1202 * @return this, for chaining, not null 1203 */ 1204 public DateTimeFormatterBuilder appendValue(TemporalField field) 1205 { 1206 assert(field, "field"); 1207 appendValue(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL)); 1208 return this; 1209 } 1210 1211 /** 1212 * Appends the value of a date-time field to the formatter using a fixed 1213 * width, zero-padded approach. 1214 * !(p) 1215 * The value of the field will be output during a format. 1216 * If the value cannot be obtained then an exception will be thrown. 1217 * !(p) 1218 * The value will be zero-padded on the left. If the size of the value 1219 * means that it cannot be printed within the width then an exception is thrown. 1220 * If the value of the field is negative then an exception is thrown during formatting. 1221 * !(p) 1222 * This method supports a special technique of parsing known as 'adjacent value parsing'. 1223 * This technique solves the problem where a value, variable or fixed width, is followed by one or more 1224 * fixed length values. The standard parser is greedy, and thus it would normally 1225 * steal the digits that are needed by the fixed width value parsers that follow the 1226 * variable width one. 1227 * !(p) 1228 * No action is required to initiate 'adjacent value parsing'. 1229 * When a call to {@code appendValue} is made, the builder 1230 * enters adjacent value parsing setup mode. If the immediately subsequent method 1231 * call or calls on the same builder are for a fixed width value, then the parser will reserve 1232 * space so that the fixed width values can be parsed. 1233 * !(p) 1234 * For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);} 1235 * The year is a variable width parse of between 1 and 19 digits. 1236 * The month is a fixed width parse of 2 digits. 1237 * Because these were appended to the same builder immediately after one another, 1238 * the year parser will reserve two digits for the month to parse. 1239 * Thus, the text '201106' will correctly parse to a year of 2011 and a month of 6. 1240 * Without adjacent value parsing, the year would greedily parse all six digits and leave 1241 * nothing for the month. 1242 * !(p) 1243 * Adjacent value parsing applies to each set of fixed width not-negative values _in the parser 1244 * that immediately follow any kind of value, variable or fixed width. 1245 * Calling any other append method will end the setup of adjacent value parsing. 1246 * Thus, _in the unlikely event that you need to avoid adjacent value parsing behavior, 1247 * simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder} 1248 * and add that to this builder. 1249 * !(p) 1250 * If adjacent parsing is active, then parsing must match exactly the specified 1251 * number of digits _in both strict and lenient modes. 1252 * In addition, no positive or negative sign is permitted. 1253 * 1254 * @param field the field to append, not null 1255 * @param width the width of the printed field, from 1 to 19 1256 * @return this, for chaining, not null 1257 * @throws IllegalArgumentException if the width is invalid 1258 */ 1259 public DateTimeFormatterBuilder appendValue(TemporalField field, int width) 1260 { 1261 assert(field, "field"); 1262 if (width < 1 || width > 19) 1263 { 1264 throw new IllegalArgumentException( 1265 "The width must be from 1 to 19 inclusive but was " ~ width.to!string); 1266 } 1267 NumberPrinterParser pp = new NumberPrinterParser(field, width, width, 1268 SignStyle.NOT_NEGATIVE); 1269 appendValue(pp); 1270 return this; 1271 } 1272 1273 /** 1274 * Appends the value of a date-time field to the formatter providing full 1275 * control over formatting. 1276 * !(p) 1277 * The value of the field will be output during a format. 1278 * If the value cannot be obtained then an exception will be thrown. 1279 * !(p) 1280 * This method provides full control of the numeric formatting, including 1281 * zero-padding and the positive/negative sign. 1282 * !(p) 1283 * The parser for a variable width value such as this normally behaves greedily, 1284 * accepting as many digits as possible. 1285 * This behavior can be affected by 'adjacent value parsing'. 1286 * See {@link #appendValue(hunt.time.temporal.TemporalField, int)} for full details. 1287 * !(p) 1288 * In strict parsing mode, the minimum number of parsed digits is {@code minWidth} 1289 * and the maximum is {@code maxWidth}. 1290 * In lenient parsing mode, the minimum number of parsed digits is one 1291 * and the maximum is 19 (except as limited by adjacent value parsing). 1292 * !(p) 1293 * If this method is invoked with equal minimum and maximum widths and a sign style of 1294 * {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}. 1295 * In this scenario, the formatting and parsing behavior described there occur. 1296 * 1297 * @param field the field to append, not null 1298 * @param minWidth the minimum field width of the printed field, from 1 to 19 1299 * @param maxWidth the maximum field width of the printed field, from 1 to 19 1300 * @param signStyle the positive/negative output style, not null 1301 * @return this, for chaining, not null 1302 * @throws IllegalArgumentException if the widths are invalid 1303 */ 1304 public DateTimeFormatterBuilder appendValue(TemporalField field, 1305 int minWidth, int maxWidth, SignStyle signStyle) 1306 { 1307 if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) 1308 { 1309 return appendValue(field, maxWidth); 1310 } 1311 assert(field, "field"); 1312 // assert(signStyle, "signStyle"); 1313 if (minWidth < 1 || minWidth > 19) 1314 { 1315 throw new IllegalArgumentException( 1316 "The minimum width must be from 1 to 19 inclusive but was " 1317 ~ minWidth.to!string); 1318 } 1319 if (maxWidth < 1 || maxWidth > 19) 1320 { 1321 throw new IllegalArgumentException( 1322 "The maximum width must be from 1 to 19 inclusive but was " 1323 ~ maxWidth.to!string); 1324 } 1325 if (maxWidth < minWidth) 1326 { 1327 throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but " 1328 ~ maxWidth.to!string ~ " < " ~ minWidth.to!string); 1329 } 1330 NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle); 1331 appendValue(pp); 1332 return this; 1333 } 1334 1335 //----------------------------------------------------------------------- 1336 /** 1337 * Appends the reduced value of a date-time field to the formatter. 1338 * !(p) 1339 * Since fields such as year vary by chronology, it is recommended to use the 1340 * {@link #appendValueReduced(TemporalField, int, int, ChronoLocalDate)} date} 1341 * variant of this method _in most cases. This variant is suitable for 1342 * simple fields or working with only the ISO chronology. 1343 * !(p) 1344 * For formatting, the {@code width} and {@code maxWidth} are used to 1345 * determine the number of characters to format. 1346 * If they are equal then the format is fixed width. 1347 * If the value of the field is within the range of the {@code baseValue} using 1348 * {@code width} characters then the reduced value is formatted otherwise the value is 1349 * truncated to fit {@code maxWidth}. 1350 * The rightmost characters are output to match the width, left padding with zero. 1351 * !(p) 1352 * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed. 1353 * For lenient parsing, the number of characters must be at least 1 and less than 10. 1354 * If the number of digits parsed is equal to {@code width} and the value is positive, 1355 * the value of the field is computed to be the first number greater than 1356 * or equal to the {@code baseValue} with the same least significant characters, 1357 * otherwise the value parsed is the field value. 1358 * This allows a reduced value to be entered for values _in range of the baseValue 1359 * and width and absolute values can be entered for values outside the range. 1360 * !(p) 1361 * For example, a base value of {@code 1980} and a width of {@code 2} will have 1362 * valid values from {@code 1980} to {@code 2079}. 1363 * During parsing, the text {@code "12"} will result _in the value {@code 2012} as that 1364 * is the value within the range where the last two characters are "12". 1365 * By contrast, parsing the text {@code "1915"} will result _in the value {@code 1915}. 1366 * 1367 * @param field the field to append, not null 1368 * @param width the field width of the printed and parsed field, from 1 to 10 1369 * @param maxWidth the maximum field width of the printed field, from 1 to 10 1370 * @param baseValue the base value of the range of valid values 1371 * @return this, for chaining, not null 1372 * @throws IllegalArgumentException if the width or base value is invalid 1373 */ 1374 public DateTimeFormatterBuilder appendValueReduced(TemporalField field, 1375 int width, int maxWidth, int baseValue) 1376 { 1377 assert(field, "field"); 1378 ReducedPrinterParser pp = new ReducedPrinterParser(field, width, 1379 maxWidth, baseValue, null); 1380 appendValue(pp); 1381 return this; 1382 } 1383 1384 /** 1385 * Appends the reduced value of a date-time field to the formatter. 1386 * !(p) 1387 * This is typically used for formatting and parsing a two digit year. 1388 * !(p) 1389 * The base date is used to calculate the full value during parsing. 1390 * For example, if the base date is 1950-01-01 then parsed values for 1391 * a two digit year parse will be _in the range 1950-01-01 to 2049-12-31. 1392 * Only the year would be extracted from the date, thus a base date of 1393 * 1950-08-25 would also parse to the range 1950-01-01 to 2049-12-31. 1394 * This behavior is necessary to support fields such as week-based-year 1395 * or other calendar systems where the parsed value does not align with 1396 * standard ISO years. 1397 * !(p) 1398 * The exact behavior is as follows. Parse the full set of fields and 1399 * determine the effective chronology using the last chronology if 1400 * it appears more than once. Then convert the base date to the 1401 * effective chronology. Then extract the specified field from the 1402 * chronology-specific base date and use it to determine the 1403 * {@code baseValue} used below. 1404 * !(p) 1405 * For formatting, the {@code width} and {@code maxWidth} are used to 1406 * determine the number of characters to format. 1407 * If they are equal then the format is fixed width. 1408 * If the value of the field is within the range of the {@code baseValue} using 1409 * {@code width} characters then the reduced value is formatted otherwise the value is 1410 * truncated to fit {@code maxWidth}. 1411 * The rightmost characters are output to match the width, left padding with zero. 1412 * !(p) 1413 * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed. 1414 * For lenient parsing, the number of characters must be at least 1 and less than 10. 1415 * If the number of digits parsed is equal to {@code width} and the value is positive, 1416 * the value of the field is computed to be the first number greater than 1417 * or equal to the {@code baseValue} with the same least significant characters, 1418 * otherwise the value parsed is the field value. 1419 * This allows a reduced value to be entered for values _in range of the baseValue 1420 * and width and absolute values can be entered for values outside the range. 1421 * !(p) 1422 * For example, a base value of {@code 1980} and a width of {@code 2} will have 1423 * valid values from {@code 1980} to {@code 2079}. 1424 * During parsing, the text {@code "12"} will result _in the value {@code 2012} as that 1425 * is the value within the range where the last two characters are "12". 1426 * By contrast, parsing the text {@code "1915"} will result _in the value {@code 1915}. 1427 * 1428 * @param field the field to append, not null 1429 * @param width the field width of the printed and parsed field, from 1 to 10 1430 * @param maxWidth the maximum field width of the printed field, from 1 to 10 1431 * @param baseDate the base date used to calculate the base value for the range 1432 * of valid values _in the parsed chronology, not null 1433 * @return this, for chaining, not null 1434 * @throws IllegalArgumentException if the width or base value is invalid 1435 */ 1436 public DateTimeFormatterBuilder appendValueReduced(TemporalField field, 1437 int width, int maxWidth, ChronoLocalDate baseDate) 1438 { 1439 assert(field, "field"); 1440 assert(baseDate, "baseDate"); 1441 ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, 0, baseDate); 1442 appendValue(pp); 1443 return this; 1444 } 1445 1446 /** 1447 * Appends a fixed or variable width printer-parser handling adjacent value mode. 1448 * If a PrinterParser is not active then the new PrinterParser becomes 1449 * the active PrinterParser. 1450 * Otherwise, the active PrinterParser is modified depending on the new PrinterParser. 1451 * If the new PrinterParser is fixed width and has sign style {@code NOT_NEGATIVE} 1452 * then its width is added to the active PP and 1453 * the new PrinterParser is forced to be fixed width. 1454 * If the new PrinterParser is variable width, the active PrinterParser is changed 1455 * to be fixed width and the new PrinterParser becomes the active PP. 1456 * 1457 * @param pp the printer-parser, not null 1458 * @return this, for chaining, not null 1459 */ 1460 private DateTimeFormatterBuilder appendValue(NumberPrinterParser pp) 1461 { 1462 if (active.valueParserIndex >= 0) 1463 { 1464 int activeValueParser = active.valueParserIndex; 1465 1466 // adjacent parsing mode, update setting _in previous parsers 1467 NumberPrinterParser basePP = cast(NumberPrinterParser) active.printerParsers.get( 1468 activeValueParser); 1469 if (pp.minWidth == pp.maxWidth && pp.signStyle == SignStyle.NOT_NEGATIVE) 1470 { 1471 // Append the width to the subsequentWidth of the active parser 1472 basePP = basePP.withSubsequentWidth(pp.maxWidth); 1473 // Append the new parser as a fixed width 1474 appendInternal(pp.withFixedWidth()); 1475 // Retain the previous active parser 1476 active.valueParserIndex = activeValueParser; 1477 } 1478 else 1479 { 1480 // Modify the active parser to be fixed width 1481 basePP = basePP.withFixedWidth(); 1482 // The new parser becomes the mew active parser 1483 active.valueParserIndex = appendInternal(pp); 1484 } 1485 // Replace the modified parser with the updated one 1486 active.printerParsers.set(activeValueParser, basePP); 1487 } 1488 else 1489 { 1490 // The new Parser becomes the active parser 1491 active.valueParserIndex = appendInternal(pp); 1492 } 1493 return this; 1494 } 1495 1496 //----------------------------------------------------------------------- 1497 /** 1498 * Appends the fractional value of a date-time field to the formatter. 1499 * !(p) 1500 * The fractional value of the field will be output including the 1501 * preceding decimal point. The preceding value is not output. 1502 * For example, the second-of-minute value of 15 would be output as {@code .25}. 1503 * !(p) 1504 * The width of the printed fraction can be controlled. Setting the 1505 * minimum width to zero will cause no output to be generated. 1506 * The printed fraction will have the minimum width necessary between 1507 * the minimum and maximum widths - trailing zeroes are omitted. 1508 * No rounding occurs due to the maximum width - digits are simply dropped. 1509 * !(p) 1510 * When parsing _in strict mode, the number of parsed digits must be between 1511 * the minimum and maximum width. In strict mode, if the minimum and maximum widths 1512 * are equal and there is no decimal point then the parser will 1513 * participate _in adjacent value parsing, see 1514 * {@link appendValue(hunt.time.temporal.TemporalField, int)}. When parsing _in lenient mode, 1515 * the minimum width is considered to be zero and the maximum is nine. 1516 * !(p) 1517 * If the value cannot be obtained then an exception will be thrown. 1518 * If the value is negative an exception will be thrown. 1519 * If the field does not have a fixed set of valid values then an 1520 * exception will be thrown. 1521 * If the field value _in the date-time to be printed is invalid it 1522 * cannot be printed and an exception will be thrown. 1523 * 1524 * @param field the field to append, not null 1525 * @param minWidth the minimum width of the field excluding the decimal point, from 0 to 9 1526 * @param maxWidth the maximum width of the field excluding the decimal point, from 1 to 9 1527 * @param decimalPoint whether to output the localized decimal point symbol 1528 * @return this, for chaining, not null 1529 * @throws IllegalArgumentException if the field has a variable set of valid values or 1530 * either width is invalid 1531 */ 1532 public DateTimeFormatterBuilder appendFraction(TemporalField field, 1533 int minWidth, int maxWidth, bool decimalPoint) 1534 { 1535 if (minWidth == maxWidth && decimalPoint == false) 1536 { 1537 // adjacent parsing 1538 appendValue(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint)); 1539 } 1540 else 1541 { 1542 appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint)); 1543 } 1544 return this; 1545 } 1546 1547 //----------------------------------------------------------------------- 1548 /** 1549 * Appends the text of a date-time field to the formatter using the full 1550 * text style. 1551 * !(p) 1552 * The text of the field will be output during a format. 1553 * The value must be within the valid range of the field. 1554 * If the value cannot be obtained then an exception will be thrown. 1555 * If the field has no textual representation, then the numeric value will be used. 1556 * !(p) 1557 * The value will be printed as per the normal format of an integer value. 1558 * Only negative numbers will be signed. No padding will be added. 1559 * 1560 * @param field the field to append, not null 1561 * @return this, for chaining, not null 1562 */ 1563 public DateTimeFormatterBuilder appendText(TemporalField field) 1564 { 1565 return appendText(field, TextStyle.FULL); 1566 } 1567 1568 /** 1569 * Appends the text of a date-time field to the formatter. 1570 * !(p) 1571 * The text of the field will be output during a format. 1572 * The value must be within the valid range of the field. 1573 * If the value cannot be obtained then an exception will be thrown. 1574 * If the field has no textual representation, then the numeric value will be used. 1575 * !(p) 1576 * The value will be printed as per the normal format of an integer value. 1577 * Only negative numbers will be signed. No padding will be added. 1578 * 1579 * @param field the field to append, not null 1580 * @param textStyle the text style to use, not null 1581 * @return this, for chaining, not null 1582 */ 1583 public DateTimeFormatterBuilder appendText(TemporalField field, TextStyle textStyle) 1584 { 1585 assert(field, "field"); 1586 // assert(textStyle, "textStyle"); 1587 appendInternal(new TextPrinterParser(field, textStyle, DateTimeTextProvider.getInstance())); 1588 return this; 1589 } 1590 1591 /** 1592 * Appends the text of a date-time field to the formatter using the specified 1593 * map to supply the text. 1594 * !(p) 1595 * The standard text outputting methods use the localized text _in the JDK. 1596 * This method allows that text to be specified directly. 1597 * The supplied map is not validated by the builder to ensure that formatting or 1598 * parsing is possible, thus an invalid map may throw an error during later use. 1599 * !(p) 1600 * Supplying the map of text provides considerable flexibility _in formatting and parsing. 1601 * For example, a legacy application might require or supply the months of the 1602 * year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text 1603 * for localized month names. Using this method, a map can be created which 1604 * defines the connection between each value and the text: 1605 * !(pre) 1606 * Map<Long, string> map = new HashMap<>(); 1607 * map.put(1L, "JNY"); 1608 * map.put(2L, "FBY"); 1609 * map.put(3L, "MCH"); 1610 * ... 1611 * builder.appendText(MONTH_OF_YEAR, map); 1612 * </pre> 1613 * !(p) 1614 * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd", 1615 * or as Roman numerals "I", "II", "III", "IV". 1616 * !(p) 1617 * During formatting, the value is obtained and checked that it is _in the valid range. 1618 * If text is not available for the value then it is output as a number. 1619 * During parsing, the parser will match against the map of text and numeric values. 1620 * 1621 * @param field the field to append, not null 1622 * @param textLookup the map from the value to the text 1623 * @return this, for chaining, not null 1624 */ 1625 public DateTimeFormatterBuilder appendText(TemporalField field, Map!(Long, string) textLookup) 1626 { 1627 assert(field, "field"); 1628 assert(textLookup, "textLookup"); 1629 Map!(Long, string) copy = new LinkedHashMap!(Long, string)(textLookup); 1630 Map!(TextStyle, Map!(Long, string)) map = /* Collections.singletonMap */ new HashMap!(TextStyle, 1631 Map!(Long, string))(); 1632 map.put(TextStyle.FULL, copy); 1633 LocaleStore store = new LocaleStore(map); 1634 DateTimeTextProvider provider = new class DateTimeTextProvider 1635 { 1636 override public string getText(Chronology chrono, 1637 TemporalField field, long value, TextStyle style, Locale locale) 1638 { 1639 return store.getText(value, style); 1640 } 1641 1642 override public string getText(TemporalField field, long value, 1643 TextStyle style, Locale locale) 1644 { 1645 return store.getText(value, style); 1646 } 1647 1648 override public Iterable!(MapEntry!(string, Long)) getTextIterator(Chronology chrono, 1649 TemporalField field, TextStyle style, Locale locale) 1650 { 1651 return store.getTextIterator(style); 1652 } 1653 1654 override public Iterable!(MapEntry!(string, Long)) getTextIterator(TemporalField field, 1655 TextStyle style, Locale locale) 1656 { 1657 return store.getTextIterator(style); 1658 } 1659 }; 1660 appendInternal(new TextPrinterParser(field, TextStyle.FULL, provider)); 1661 return this; 1662 } 1663 1664 //----------------------------------------------------------------------- 1665 /** 1666 * Appends an instant using ISO-8601 to the formatter, formatting fractional 1667 * digits _in groups of three. 1668 * !(p) 1669 * Instants have a fixed output format. 1670 * They are converted to a date-time with a zone-offset of UTC and formatted 1671 * using the standard ISO-8601 format. 1672 * With this method, formatting nano-of-second outputs zero, three, six 1673 * or nine digits as necessary. 1674 * The localized decimal style is not used. 1675 * !(p) 1676 * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} 1677 * and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS} 1678 * may be outside the maximum range of {@code LocalDateTime}. 1679 * !(p) 1680 * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing. 1681 * The end-of-day time of '24:00' is handled as midnight at the start of the following day. 1682 * The leap-second time of '23:59:59' is handled to some degree, see 1683 * {@link DateTimeFormatter#parsedLeapSecond()} for full details. 1684 * !(p) 1685 * When formatting, the instant will always be suffixed by 'Z' to indicate UTC. 1686 * When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()} 1687 * will be used to parse the offset, converting the instant to UTC as necessary. 1688 * !(p) 1689 * An alternative to this method is to format/parse the instant as a single 1690 * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}. 1691 * 1692 * @return this, for chaining, not null 1693 */ 1694 public DateTimeFormatterBuilder appendInstant() 1695 { 1696 appendInternal(new InstantPrinterParser(-2)); 1697 return this; 1698 } 1699 1700 /** 1701 * Appends an instant using ISO-8601 to the formatter with control over 1702 * the number of fractional digits. 1703 * !(p) 1704 * Instants have a fixed output format, although this method provides some 1705 * control over the fractional digits. They are converted to a date-time 1706 * with a zone-offset of UTC and printed using the standard ISO-8601 format. 1707 * The localized decimal style is not used. 1708 * !(p) 1709 * The {@code fractionalDigits} parameter allows the output of the fractional 1710 * second to be controlled. Specifying zero will cause no fractional digits 1711 * to be output. From 1 to 9 will output an increasing number of digits, using 1712 * zero right-padding if necessary. The special value -1 is used to output as 1713 * many digits as necessary to avoid any trailing zeroes. 1714 * !(p) 1715 * When parsing _in strict mode, the number of parsed digits must match the 1716 * fractional digits. When parsing _in lenient mode, any number of fractional 1717 * digits from zero to nine are accepted. 1718 * !(p) 1719 * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} 1720 * and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS} 1721 * may be outside the maximum range of {@code LocalDateTime}. 1722 * !(p) 1723 * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing. 1724 * The end-of-day time of '24:00' is handled as midnight at the start of the following day. 1725 * The leap-second time of '23:59:60' is handled to some degree, see 1726 * {@link DateTimeFormatter#parsedLeapSecond()} for full details. 1727 * !(p) 1728 * An alternative to this method is to format/parse the instant as a single 1729 * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}. 1730 * 1731 * @param fractionalDigits the number of fractional second digits to format with, 1732 * from 0 to 9, or -1 to use as many digits as necessary 1733 * @return this, for chaining, not null 1734 * @throws IllegalArgumentException if the number of fractional digits is invalid 1735 */ 1736 public DateTimeFormatterBuilder appendInstant(int fractionalDigits) 1737 { 1738 if (fractionalDigits < -1 || fractionalDigits > 9) 1739 { 1740 throw new IllegalArgumentException( 1741 "The fractional digits must be from -1 to 9 inclusive but was " 1742 ~ fractionalDigits.to!string); 1743 } 1744 appendInternal(new InstantPrinterParser(fractionalDigits)); 1745 return this; 1746 } 1747 1748 //----------------------------------------------------------------------- 1749 /** 1750 * Appends the zone offset, such as '+01:00', to the formatter. 1751 * !(p) 1752 * This appends an instruction to format/parse the offset ID to the builder. 1753 * This is equivalent to calling {@code appendOffset("+HH:mm:ss", "Z")}. 1754 * See {@link #appendOffset(string, string)} for details on formatting 1755 * and parsing. 1756 * 1757 * @return this, for chaining, not null 1758 */ 1759 public DateTimeFormatterBuilder appendOffsetId() 1760 { 1761 appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z); 1762 return this; 1763 } 1764 1765 /** 1766 * Appends the zone offset, such as '+01:00', to the formatter. 1767 * !(p) 1768 * This appends an instruction to format/parse the offset ID to the builder. 1769 * !(p) 1770 * During formatting, the offset is obtained using a mechanism equivalent 1771 * to querying the temporal with {@link TemporalQueries#offset()}. 1772 * It will be printed using the format defined below. 1773 * If the offset cannot be obtained then an exception is thrown unless the 1774 * section of the formatter is optional. 1775 * !(p) 1776 * When parsing _in strict mode, the input must contain the mandatory 1777 * and optional elements are defined by the specified pattern. 1778 * If the offset cannot be parsed then an exception is thrown unless 1779 * the section of the formatter is optional. 1780 * !(p) 1781 * When parsing _in lenient mode, only the hours are mandatory - minutes 1782 * and seconds are optional. The colons are required if the specified 1783 * pattern contains a colon. If the specified pattern is "+HH", the 1784 * presence of colons is determined by whether the character after the 1785 * hour digits is a colon or not. 1786 * If the offset cannot be parsed then an exception is thrown unless 1787 * the section of the formatter is optional. 1788 * !(p) 1789 * The format of the offset is controlled by a pattern which must be one 1790 * of the following: 1791 * !(ul) 1792 * !(li){@code +HH} - hour only, ignoring minute and second 1793 * !(li){@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon 1794 * !(li){@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon 1795 * !(li){@code +HHMM} - hour and minute, ignoring second, no colon 1796 * !(li){@code +HH:MM} - hour and minute, ignoring second, with colon 1797 * !(li){@code +HHMMss} - hour and minute, with second if non-zero, no colon 1798 * !(li){@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon 1799 * !(li){@code +HHMMSS} - hour, minute and second, no colon 1800 * !(li){@code +HH:MM:SS} - hour, minute and second, with colon 1801 * !(li){@code +HHmmss} - hour, with minute if non-zero or with minute and 1802 * second if non-zero, no colon 1803 * !(li){@code +HH:mm:ss} - hour, with minute if non-zero or with minute and 1804 * second if non-zero, with colon 1805 * !(li){@code +H} - hour only, ignoring minute and second 1806 * !(li){@code +Hmm} - hour, with minute if non-zero, ignoring second, no colon 1807 * !(li){@code +H:mm} - hour, with minute if non-zero, ignoring second, with colon 1808 * !(li){@code +HMM} - hour and minute, ignoring second, no colon 1809 * !(li){@code +H:MM} - hour and minute, ignoring second, with colon 1810 * !(li){@code +HMMss} - hour and minute, with second if non-zero, no colon 1811 * !(li){@code +H:MM:ss} - hour and minute, with second if non-zero, with colon 1812 * !(li){@code +HMMSS} - hour, minute and second, no colon 1813 * !(li){@code +H:MM:SS} - hour, minute and second, with colon 1814 * !(li){@code +Hmmss} - hour, with minute if non-zero or with minute and 1815 * second if non-zero, no colon 1816 * !(li){@code +H:mm:ss} - hour, with minute if non-zero or with minute and 1817 * second if non-zero, with colon 1818 * </ul> 1819 * Patterns containing "HH" will format and parse a two digit hour, 1820 * zero-padded if necessary. Patterns containing "H" will format with no 1821 * zero-padding, and parse either one or two digits. 1822 * In lenient mode, the parser will be greedy and parse the maximum digits possible. 1823 * The "no offset" text controls what text is printed when the total amount of 1824 * the offset fields to be output is zero. 1825 * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'. 1826 * Three formats are accepted for parsing UTC - the "no offset" text, and the 1827 * plus and minus versions of zero defined by the pattern. 1828 * 1829 * @param pattern the pattern to use, not null 1830 * @param noOffsetText the text to use when the offset is zero, not null 1831 * @return this, for chaining, not null 1832 * @throws IllegalArgumentException if the pattern is invalid 1833 */ 1834 public DateTimeFormatterBuilder appendOffset(string pattern, string noOffsetText) 1835 { 1836 appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText)); 1837 return this; 1838 } 1839 1840 /** 1841 * Appends the localized zone offset, such as 'GMT+01:00', to the formatter. 1842 * !(p) 1843 * This appends a localized zone offset to the builder, the format of the 1844 * localized offset is controlled by the specified {@link FormatStyle style} 1845 * to this method: 1846 * !(ul) 1847 * !(li){@link TextStyle#FULL full} - formats with localized offset text, such 1848 * as 'GMT, 2-digit hour and minute field, optional second field if non-zero, 1849 * and colon. 1850 * !(li){@link TextStyle#SHORT short} - formats with localized offset text, 1851 * such as 'GMT, hour without leading zero, optional 2-digit minute and 1852 * second if non-zero, and colon. 1853 * </ul> 1854 * !(p) 1855 * During formatting, the offset is obtained using a mechanism equivalent 1856 * to querying the temporal with {@link TemporalQueries#offset()}. 1857 * If the offset cannot be obtained then an exception is thrown unless the 1858 * section of the formatter is optional. 1859 * !(p) 1860 * During parsing, the offset is parsed using the format defined above. 1861 * If the offset cannot be parsed then an exception is thrown unless the 1862 * section of the formatter is optional. 1863 * 1864 * @param style the format style to use, not null 1865 * @return this, for chaining, not null 1866 * @throws IllegalArgumentException if style is neither {@link TextStyle#FULL 1867 * full} nor {@link TextStyle#SHORT short} 1868 */ 1869 public DateTimeFormatterBuilder appendLocalizedOffset(TextStyle style) 1870 { 1871 // assert(style, "style"); 1872 if (style != TextStyle.FULL && style != TextStyle.SHORT) 1873 { 1874 throw new IllegalArgumentException("Style must be either full or short"); 1875 } 1876 appendInternal(new LocalizedOffsetIdPrinterParser(style)); 1877 return this; 1878 } 1879 1880 //----------------------------------------------------------------------- 1881 /** 1882 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. 1883 * !(p) 1884 * This appends an instruction to format/parse the zone ID to the builder. 1885 * The zone ID is obtained _in a strict manner suitable for {@code ZonedDateTime}. 1886 * By contrast, {@code OffsetDateTime} does not have a zone ID suitable 1887 * for use with this method, see {@link #appendZoneOrOffsetId()}. 1888 * !(p) 1889 * During formatting, the zone is obtained using a mechanism equivalent 1890 * to querying the temporal with {@link TemporalQueries#zoneId()}. 1891 * It will be printed using the result of {@link ZoneId#getId()}. 1892 * If the zone cannot be obtained then an exception is thrown unless the 1893 * section of the formatter is optional. 1894 * !(p) 1895 * During parsing, the text must match a known zone or offset. 1896 * There are two types of zone ID, offset-based, such as '+01:30' and 1897 * region-based, such as 'Europe/London'. These are parsed differently. 1898 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser 1899 * expects an offset-based zone and will not match region-based zones. 1900 * The offset ID, such as '+02:30', may be at the start of the parse, 1901 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is 1902 * equivalent to using {@link #appendOffset(string, string)} using the 1903 * arguments 'HH:MM:ss' and the no offset string '0'. 1904 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot 1905 * match a following offset ID, then {@link ZoneOffset#UTC} is selected. 1906 * In all other cases, the list of known region-based zones is used to 1907 * find the longest available match. If no match is found, and the parse 1908 * starts with 'Z', then {@code ZoneOffset.UTC} is selected. 1909 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting. 1910 * !(p) 1911 * For example, the following will parse: 1912 * !(pre) 1913 * "Europe/London" -- ZoneId.of("Europe/London") 1914 * "Z" -- ZoneOffset.UTC 1915 * "UT" -- ZoneId.of("UT") 1916 * "UTC" -- ZoneId.of("UTC") 1917 * "GMT" -- ZoneId.of("GMT") 1918 * "+01:30" -- ZoneOffset.of("+01:30") 1919 * "UT+01:30" -- ZoneOffset.of("+01:30") 1920 * "UTC+01:30" -- ZoneOffset.of("+01:30") 1921 * "GMT+01:30" -- ZoneOffset.of("+01:30") 1922 * </pre> 1923 * 1924 * @return this, for chaining, not null 1925 * @see #appendZoneRegionId() 1926 */ 1927 public DateTimeFormatterBuilder appendZoneId() 1928 { 1929 appendInternal(new ZoneIdPrinterParser(TemporalQueries.zoneId(), "ZoneId()")); 1930 return this; 1931 } 1932 1933 /** 1934 * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter, 1935 * rejecting the zone ID if it is a {@code ZoneOffset}. 1936 * !(p) 1937 * This appends an instruction to format/parse the zone ID to the builder 1938 * only if it is a region-based ID. 1939 * !(p) 1940 * During formatting, the zone is obtained using a mechanism equivalent 1941 * to querying the temporal with {@link TemporalQueries#zoneId()}. 1942 * If the zone is a {@code ZoneOffset} or it cannot be obtained then 1943 * an exception is thrown unless the section of the formatter is optional. 1944 * If the zone is not an offset, then the zone will be printed using 1945 * the zone ID from {@link ZoneId#getId()}. 1946 * !(p) 1947 * During parsing, the text must match a known zone or offset. 1948 * There are two types of zone ID, offset-based, such as '+01:30' and 1949 * region-based, such as 'Europe/London'. These are parsed differently. 1950 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser 1951 * expects an offset-based zone and will not match region-based zones. 1952 * The offset ID, such as '+02:30', may be at the start of the parse, 1953 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is 1954 * equivalent to using {@link #appendOffset(string, string)} using the 1955 * arguments 'HH:MM:ss' and the no offset string '0'. 1956 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot 1957 * match a following offset ID, then {@link ZoneOffset#UTC} is selected. 1958 * In all other cases, the list of known region-based zones is used to 1959 * find the longest available match. If no match is found, and the parse 1960 * starts with 'Z', then {@code ZoneOffset.UTC} is selected. 1961 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting. 1962 * !(p) 1963 * For example, the following will parse: 1964 * !(pre) 1965 * "Europe/London" -- ZoneId.of("Europe/London") 1966 * "Z" -- ZoneOffset.UTC 1967 * "UT" -- ZoneId.of("UT") 1968 * "UTC" -- ZoneId.of("UTC") 1969 * "GMT" -- ZoneId.of("GMT") 1970 * "+01:30" -- ZoneOffset.of("+01:30") 1971 * "UT+01:30" -- ZoneOffset.of("+01:30") 1972 * "UTC+01:30" -- ZoneOffset.of("+01:30") 1973 * "GMT+01:30" -- ZoneOffset.of("+01:30") 1974 * </pre> 1975 * !(p) 1976 * Note that this method is identical to {@code appendZoneId()} except 1977 * _in the mechanism used to obtain the zone. 1978 * Note also that parsing accepts offsets, whereas formatting will never 1979 * produce one. 1980 * 1981 * @return this, for chaining, not null 1982 * @see #appendZoneId() 1983 */ 1984 public DateTimeFormatterBuilder appendZoneRegionId() 1985 { 1986 appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()")); 1987 return this; 1988 } 1989 1990 /** 1991 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to 1992 * the formatter, using the best available zone ID. 1993 * !(p) 1994 * This appends an instruction to format/parse the best available 1995 * zone or offset ID to the builder. 1996 * The zone ID is obtained _in a lenient manner that first attempts to 1997 * find a true zone ID, such as that on {@code ZonedDateTime}, and 1998 * then attempts to find an offset, such as that on {@code OffsetDateTime}. 1999 * !(p) 2000 * During formatting, the zone is obtained using a mechanism equivalent 2001 * to querying the temporal with {@link TemporalQueries#zone()}. 2002 * It will be printed using the result of {@link ZoneId#getId()}. 2003 * If the zone cannot be obtained then an exception is thrown unless the 2004 * section of the formatter is optional. 2005 * !(p) 2006 * During parsing, the text must match a known zone or offset. 2007 * There are two types of zone ID, offset-based, such as '+01:30' and 2008 * region-based, such as 'Europe/London'. These are parsed differently. 2009 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser 2010 * expects an offset-based zone and will not match region-based zones. 2011 * The offset ID, such as '+02:30', may be at the start of the parse, 2012 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is 2013 * equivalent to using {@link #appendOffset(string, string)} using the 2014 * arguments 'HH:MM:ss' and the no offset string '0'. 2015 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot 2016 * match a following offset ID, then {@link ZoneOffset#UTC} is selected. 2017 * In all other cases, the list of known region-based zones is used to 2018 * find the longest available match. If no match is found, and the parse 2019 * starts with 'Z', then {@code ZoneOffset.UTC} is selected. 2020 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting. 2021 * !(p) 2022 * For example, the following will parse: 2023 * !(pre) 2024 * "Europe/London" -- ZoneId.of("Europe/London") 2025 * "Z" -- ZoneOffset.UTC 2026 * "UT" -- ZoneId.of("UT") 2027 * "UTC" -- ZoneId.of("UTC") 2028 * "GMT" -- ZoneId.of("GMT") 2029 * "+01:30" -- ZoneOffset.of("+01:30") 2030 * "UT+01:30" -- ZoneOffset.of("UT+01:30") 2031 * "UTC+01:30" -- ZoneOffset.of("UTC+01:30") 2032 * "GMT+01:30" -- ZoneOffset.of("GMT+01:30") 2033 * </pre> 2034 * !(p) 2035 * Note that this method is identical to {@code appendZoneId()} except 2036 * _in the mechanism used to obtain the zone. 2037 * 2038 * @return this, for chaining, not null 2039 * @see #appendZoneId() 2040 */ 2041 public DateTimeFormatterBuilder appendZoneOrOffsetId() 2042 { 2043 appendInternal(new ZoneIdPrinterParser(TemporalQueries.zone(), "ZoneOrOffsetId()")); 2044 return this; 2045 } 2046 2047 /** 2048 * Appends the time-zone name, such as 'British Summer Time', to the formatter. 2049 * !(p) 2050 * This appends an instruction to format/parse the textual name of the zone to 2051 * the builder. 2052 * !(p) 2053 * During formatting, the zone is obtained using a mechanism equivalent 2054 * to querying the temporal with {@link TemporalQueries#zoneId()}. 2055 * If the zone is a {@code ZoneOffset} it will be printed using the 2056 * result of {@link ZoneOffset#getId()}. 2057 * If the zone is not an offset, the textual name will be looked up 2058 * for the locale set _in the {@link DateTimeFormatter}. 2059 * If the temporal object being printed represents an instant, or if it is a 2060 * local date-time that is not _in a daylight saving gap or overlap then 2061 * the text will be the summer or winter time text as appropriate. 2062 * If the lookup for text does not find any suitable result, then the 2063 * {@link ZoneId#getId() ID} will be printed. 2064 * If the zone cannot be obtained then an exception is thrown unless the 2065 * section of the formatter is optional. 2066 * !(p) 2067 * During parsing, either the textual zone name, the zone ID or the offset 2068 * is accepted. Many textual zone names are not unique, such as CST can be 2069 * for both "Central Standard Time" and "China Standard Time". In this 2070 * situation, the zone id will be determined by the region information from 2071 * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard 2072 * zone id for that area, for example, America/New_York for the America Eastern 2073 * zone. The {@link #appendZoneText(TextStyle, Set)} may be used 2074 * to specify a set of preferred {@link ZoneId} _in this situation. 2075 * 2076 * @param textStyle the text style to use, not null 2077 * @return this, for chaining, not null 2078 */ 2079 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) 2080 { 2081 appendInternal(new ZoneTextPrinterParser(textStyle, null, false)); 2082 return this; 2083 } 2084 2085 /** 2086 * Appends the time-zone name, such as 'British Summer Time', to the formatter. 2087 * !(p) 2088 * This appends an instruction to format/parse the textual name of the zone to 2089 * the builder. 2090 * !(p) 2091 * During formatting, the zone is obtained using a mechanism equivalent 2092 * to querying the temporal with {@link TemporalQueries#zoneId()}. 2093 * If the zone is a {@code ZoneOffset} it will be printed using the 2094 * result of {@link ZoneOffset#getId()}. 2095 * If the zone is not an offset, the textual name will be looked up 2096 * for the locale set _in the {@link DateTimeFormatter}. 2097 * If the temporal object being printed represents an instant, or if it is a 2098 * local date-time that is not _in a daylight saving gap or overlap, then the text 2099 * will be the summer or winter time text as appropriate. 2100 * If the lookup for text does not find any suitable result, then the 2101 * {@link ZoneId#getId() ID} will be printed. 2102 * If the zone cannot be obtained then an exception is thrown unless the 2103 * section of the formatter is optional. 2104 * !(p) 2105 * During parsing, either the textual zone name, the zone ID or the offset 2106 * is accepted. Many textual zone names are not unique, such as CST can be 2107 * for both "Central Standard Time" and "China Standard Time". In this 2108 * situation, the zone id will be determined by the region information from 2109 * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard 2110 * zone id for that area, for example, America/New_York for the America Eastern 2111 * zone. This method also allows a set of preferred {@link ZoneId} to be 2112 * specified for parsing. The matched preferred zone id will be used if the 2113 * textural zone name being parsed is not unique. 2114 * !(p) 2115 * If the zone cannot be parsed then an exception is thrown unless the 2116 * section of the formatter is optional. 2117 * 2118 * @param textStyle the text style to use, not null 2119 * @param preferredZones the set of preferred zone ids, not null 2120 * @return this, for chaining, not null 2121 */ 2122 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle, Set!(ZoneId) preferredZones) 2123 { 2124 assert(preferredZones, "preferredZones"); 2125 appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones, false)); 2126 return this; 2127 } 2128 //---------------------------------------------------------------------- 2129 /** 2130 * Appends the generic time-zone name, such as 'Pacific Time', to the formatter. 2131 * !(p) 2132 * This appends an instruction to format/parse the generic textual 2133 * name of the zone to the builder. The generic name is the same throughout the whole 2134 * year, ignoring any daylight saving changes. For example, 'Pacific Time' is the 2135 * generic name, whereas 'Pacific Standard Time' and 'Pacific Daylight Time' are the 2136 * specific names, see {@link #appendZoneText(TextStyle)}. 2137 * !(p) 2138 * During formatting, the zone is obtained using a mechanism equivalent 2139 * to querying the temporal with {@link TemporalQueries#zoneId()}. 2140 * If the zone is a {@code ZoneOffset} it will be printed using the 2141 * result of {@link ZoneOffset#getId()}. 2142 * If the zone is not an offset, the textual name will be looked up 2143 * for the locale set _in the {@link DateTimeFormatter}. 2144 * If the lookup for text does not find any suitable result, then the 2145 * {@link ZoneId#getId() ID} will be printed. 2146 * If the zone cannot be obtained then an exception is thrown unless the 2147 * section of the formatter is optional. 2148 * !(p) 2149 * During parsing, either the textual zone name, the zone ID or the offset 2150 * is accepted. Many textual zone names are not unique, such as CST can be 2151 * for both "Central Standard Time" and "China Standard Time". In this 2152 * situation, the zone id will be determined by the region information from 2153 * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard 2154 * zone id for that area, for example, America/New_York for the America Eastern zone. 2155 * The {@link #appendGenericZoneText(TextStyle, Set)} may be used 2156 * to specify a set of preferred {@link ZoneId} _in this situation. 2157 * 2158 * @param textStyle the text style to use, not null 2159 * @return this, for chaining, not null 2160 * @since 9 2161 */ 2162 public DateTimeFormatterBuilder appendGenericZoneText(TextStyle textStyle) 2163 { 2164 appendInternal(new ZoneTextPrinterParser(textStyle, null, true)); 2165 return this; 2166 } 2167 2168 /** 2169 * Appends the generic time-zone name, such as 'Pacific Time', to the formatter. 2170 * !(p) 2171 * This appends an instruction to format/parse the generic textual 2172 * name of the zone to the builder. The generic name is the same throughout the whole 2173 * year, ignoring any daylight saving changes. For example, 'Pacific Time' is the 2174 * generic name, whereas 'Pacific Standard Time' and 'Pacific Daylight Time' are the 2175 * specific names, see {@link #appendZoneText(TextStyle)}. 2176 * !(p) 2177 * This method also allows a set of preferred {@link ZoneId} to be 2178 * specified for parsing. The matched preferred zone id will be used if the 2179 * textural zone name being parsed is not unique. 2180 * !(p) 2181 * See {@link #appendGenericZoneText(TextStyle)} for details about 2182 * formatting and parsing. 2183 * 2184 * @param textStyle the text style to use, not null 2185 * @param preferredZones the set of preferred zone ids, not null 2186 * @return this, for chaining, not null 2187 * @since 9 2188 */ 2189 public DateTimeFormatterBuilder appendGenericZoneText(TextStyle textStyle, 2190 Set!(ZoneId) preferredZones) 2191 { 2192 appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones, true)); 2193 return this; 2194 } 2195 2196 //----------------------------------------------------------------------- 2197 /** 2198 * Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter. 2199 * !(p) 2200 * This appends an instruction to format/parse the chronology ID to the builder. 2201 * !(p) 2202 * During formatting, the chronology is obtained using a mechanism equivalent 2203 * to querying the temporal with {@link TemporalQueries#chronology()}. 2204 * It will be printed using the result of {@link Chronology#getId()}. 2205 * If the chronology cannot be obtained then an exception is thrown unless the 2206 * section of the formatter is optional. 2207 * !(p) 2208 * During parsing, the chronology is parsed and must match one of the chronologies 2209 * _in {@link Chronology#getAvailableChronologies()}. 2210 * If the chronology cannot be parsed then an exception is thrown unless the 2211 * section of the formatter is optional. 2212 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting. 2213 * 2214 * @return this, for chaining, not null 2215 */ 2216 public DateTimeFormatterBuilder appendChronologyId() 2217 { 2218 appendInternal(new ChronoPrinterParser(null)); 2219 return this; 2220 } 2221 2222 /** 2223 * Appends the chronology name to the formatter. 2224 * !(p) 2225 * The calendar system name will be output during a format. 2226 * If the chronology cannot be obtained then an exception will be thrown. 2227 * 2228 * @param textStyle the text style to use, not null 2229 * @return this, for chaining, not null 2230 */ 2231 public DateTimeFormatterBuilder appendChronologyText(TextStyle textStyle) 2232 { 2233 assert(textStyle, "textStyle"); 2234 appendInternal(new ChronoPrinterParser(textStyle)); 2235 return this; 2236 } 2237 2238 //----------------------------------------------------------------------- 2239 /** 2240 * Appends a localized date-time pattern to the formatter. 2241 * !(p) 2242 * This appends a localized section to the builder, suitable for outputting 2243 * a date, time or date-time combination. The format of the localized 2244 * section is lazily looked up based on four items: 2245 * !(ul) 2246 * !(li)the {@code dateStyle} specified to this method 2247 * !(li)the {@code timeStyle} specified to this method 2248 * !(li)the {@code Locale} of the {@code DateTimeFormatter} 2249 * !(li)the {@code Chronology}, selecting the best available 2250 * </ul> 2251 * During formatting, the chronology is obtained from the temporal object 2252 * being formatted, which may have been overridden by 2253 * {@link DateTimeFormatter#withChronology(Chronology)}. 2254 * The {@code FULL} and {@code LONG} styles typically require a time-zone. 2255 * When formatting using these styles, a {@code ZoneId} must be available, 2256 * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}. 2257 * !(p) 2258 * During parsing, if a chronology has already been parsed, then it is used. 2259 * Otherwise the default from {@code DateTimeFormatter.withChronology(Chronology)} 2260 * is used, with {@code IsoChronology} as the fallback. 2261 * !(p) 2262 * Note that this method provides similar functionality to methods on 2263 * {@code DateFormat} such as {@link java.text.DateFormat#getDateTimeInstance(int, int)}. 2264 * 2265 * @param dateStyle the date style to use, null means no date required 2266 * @param timeStyle the time style to use, null means no time required 2267 * @return this, for chaining, not null 2268 * @throws IllegalArgumentException if both the date and time styles are null 2269 */ 2270 public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) 2271 { 2272 if (dateStyle is null && timeStyle is null) 2273 { 2274 throw new IllegalArgumentException("Either the date or time style must be non-null"); 2275 } 2276 appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle)); 2277 return this; 2278 } 2279 2280 //----------------------------------------------------------------------- 2281 /** 2282 * Appends a character literal to the formatter. 2283 * !(p) 2284 * This character will be output during a format. 2285 * 2286 * @param literal the literal to append, not null 2287 * @return this, for chaining, not null 2288 */ 2289 public DateTimeFormatterBuilder appendLiteral(char literal) 2290 { 2291 appendInternal(new CharLiteralPrinterParser(literal)); 2292 return this; 2293 } 2294 2295 /** 2296 * Appends a string literal to the formatter. 2297 * !(p) 2298 * This string will be output during a format. 2299 * !(p) 2300 * If the literal is empty, nothing is added to the formatter. 2301 * 2302 * @param literal the literal to append, not null 2303 * @return this, for chaining, not null 2304 */ 2305 public DateTimeFormatterBuilder appendLiteral(string literal) 2306 { 2307 assert(literal, "literal"); 2308 if (literal.length > 0) 2309 { 2310 if (literal.length == 1) 2311 { 2312 appendInternal(new CharLiteralPrinterParser(literal[0])); 2313 } 2314 else 2315 { 2316 appendInternal(new StringLiteralPrinterParser(literal)); 2317 } 2318 } 2319 return this; 2320 } 2321 2322 //----------------------------------------------------------------------- 2323 /** 2324 * Appends all the elements of a formatter to the builder. 2325 * !(p) 2326 * This method has the same effect as appending each of the constituent 2327 * parts of the formatter directly to this builder. 2328 * 2329 * @param formatter the formatter to add, not null 2330 * @return this, for chaining, not null 2331 */ 2332 public DateTimeFormatterBuilder append(DateTimeFormatter formatter) 2333 { 2334 assert(formatter, "formatter"); 2335 appendInternal(formatter.toPrinterParser(false)); 2336 return this; 2337 } 2338 2339 /** 2340 * Appends a formatter to the builder which will optionally format/parse. 2341 * !(p) 2342 * This method has the same effect as appending each of the constituent 2343 * parts directly to this builder surrounded by an {@link #optionalStart()} and 2344 * {@link #optionalEnd()}. 2345 * !(p) 2346 * The formatter will format if data is available for all the fields contained within it. 2347 * The formatter will parse if the string matches, otherwise no error is returned. 2348 * 2349 * @param formatter the formatter to add, not null 2350 * @return this, for chaining, not null 2351 */ 2352 public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter) 2353 { 2354 assert(formatter, "formatter"); 2355 appendInternal(formatter.toPrinterParser(true)); 2356 return this; 2357 } 2358 2359 //----------------------------------------------------------------------- 2360 /** 2361 * Appends the elements defined by the specified pattern to the builder. 2362 * !(p) 2363 * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. 2364 * The characters '#', '{' and '}' are reserved for future use. 2365 * The characters '[' and ']' indicate optional patterns. 2366 * The following pattern letters are defined: 2367 * !(pre) 2368 * Symbol Meaning Presentation Examples 2369 * ------ ------- ------------ ------- 2370 * G era text AD; Anno Domini; A 2371 * u year year 2004; 04 2372 * y year-of-era year 2004; 04 2373 * D day-of-year number 189 2374 * M/L month-of-year number/text 7; 07; Jul; July; J 2375 * d day-of-month number 10 2376 * g modified-julian-day number 2451334 2377 * 2378 * Q/q quarter-of-year number/text 3; 03; Q3; 3rd quarter 2379 * Y week-based-year year 1996; 96 2380 * w week-of-week-based-year number 27 2381 * W week-of-month number 4 2382 * E day-of-week text Tue; Tuesday; T 2383 * e/c localized day-of-week number/text 2; 02; Tue; Tuesday; T 2384 * F day-of-week-_in-month number 3 2385 * 2386 * a am-pm-of-day text PM 2387 * h clock-hour-of-am-pm (1-12) number 12 2388 * K hour-of-am-pm (0-11) number 0 2389 * k clock-hour-of-day (1-24) number 24 2390 * 2391 * H hour-of-day (0-23) number 0 2392 * m minute-of-hour number 30 2393 * s second-of-minute number 55 2394 * S fraction-of-second fraction 978 2395 * A milli-of-day number 1234 2396 * n nano-of-second number 987654321 2397 * N nano-of-day number 1234000000 2398 * 2399 * V time-zone ID zone-id America/Los_Angeles; Z; -08:30 2400 * v generic time-zone name zone-name PT, Pacific Time 2401 * z time-zone name zone-name Pacific Standard Time; PST 2402 * O localized zone-offset offset-O GMT+8; GMT+08:00; UTC-08:00; 2403 * X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15 2404 * x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15 2405 * Z zone-offset offset-Z +0000; -0800; -08:00 2406 * 2407 * p pad next pad modifier 1 2408 * 2409 * ' escape for text delimiter 2410 * '' single quote literal ' 2411 * [ optional section start 2412 * ] optional section end 2413 * # reserved for future use 2414 * { reserved for future use 2415 * } reserved for future use 2416 * </pre> 2417 * !(p) 2418 * The count of pattern letters determine the format. 2419 * See <a href="DateTimeFormatter.html#patterns">DateTimeFormatter</a> for a user-focused description of the patterns. 2420 * The following tables define how the pattern letters map to the builder. 2421 * !(p) 2422 * !(b)Date fields</b>: Pattern letters to output a date. 2423 * !(pre) 2424 * Pattern Count Equivalent builder methods 2425 * ------- ----- -------------------------- 2426 * G 1 appendText(ChronoField.ERA, TextStyle.SHORT) 2427 * GG 2 appendText(ChronoField.ERA, TextStyle.SHORT) 2428 * GGG 3 appendText(ChronoField.ERA, TextStyle.SHORT) 2429 * GGGG 4 appendText(ChronoField.ERA, TextStyle.FULL) 2430 * GGGGG 5 appendText(ChronoField.ERA, TextStyle.NARROW) 2431 * 2432 * u 1 appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL) 2433 * uu 2 appendValueReduced(ChronoField.YEAR, 2, 2000) 2434 * uuu 3 appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL) 2435 * u..u 4..n appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD) 2436 * y 1 appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL) 2437 * yy 2 appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000) 2438 * yyy 3 appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL) 2439 * y..y 4..n appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD) 2440 * Y 1 append special localized WeekFields element for numeric week-based-year 2441 * YY 2 append special localized WeekFields element for reduced numeric week-based-year 2 digits 2442 * YYY 3 append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL) 2443 * Y..Y 4..n append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD) 2444 * 2445 * Q 1 appendValue(IsoFields.QUARTER_OF_YEAR) 2446 * QQ 2 appendValue(IsoFields.QUARTER_OF_YEAR, 2) 2447 * QQQ 3 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT) 2448 * QQQQ 4 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL) 2449 * QQQQQ 5 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW) 2450 * q 1 appendValue(IsoFields.QUARTER_OF_YEAR) 2451 * qq 2 appendValue(IsoFields.QUARTER_OF_YEAR, 2) 2452 * qqq 3 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE) 2453 * qqqq 4 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE) 2454 * qqqqq 5 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE) 2455 * 2456 * M 1 appendValue(ChronoField.MONTH_OF_YEAR) 2457 * MM 2 appendValue(ChronoField.MONTH_OF_YEAR, 2) 2458 * MMM 3 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT) 2459 * MMMM 4 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL) 2460 * MMMMM 5 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW) 2461 * L 1 appendValue(ChronoField.MONTH_OF_YEAR) 2462 * LL 2 appendValue(ChronoField.MONTH_OF_YEAR, 2) 2463 * LLL 3 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE) 2464 * LLLL 4 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE) 2465 * LLLLL 5 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE) 2466 * 2467 * w 1 append special localized WeekFields element for numeric week-of-year 2468 * ww 2 append special localized WeekFields element for numeric week-of-year, zero-padded 2469 * W 1 append special localized WeekFields element for numeric week-of-month 2470 * d 1 appendValue(ChronoField.DAY_OF_MONTH) 2471 * dd 2 appendValue(ChronoField.DAY_OF_MONTH, 2) 2472 * D 1 appendValue(ChronoField.DAY_OF_YEAR) 2473 * DD 2 appendValue(ChronoField.DAY_OF_YEAR, 2, 3, SignStyle.NOT_NEGATIVE) 2474 * DDD 3 appendValue(ChronoField.DAY_OF_YEAR, 3) 2475 * F 1 appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH) 2476 * g..g 1..n appendValue(JulianFields.MODIFIED_JULIAN_DAY, n, 19, SignStyle.NORMAL) 2477 * E 1 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT) 2478 * EE 2 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT) 2479 * EEE 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT) 2480 * EEEE 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL) 2481 * EEEEE 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW) 2482 * e 1 append special localized WeekFields element for numeric day-of-week 2483 * ee 2 append special localized WeekFields element for numeric day-of-week, zero-padded 2484 * eee 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT) 2485 * eeee 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL) 2486 * eeeee 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW) 2487 * c 1 append special localized WeekFields element for numeric day-of-week 2488 * ccc 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE) 2489 * cccc 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE) 2490 * ccccc 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE) 2491 * </pre> 2492 * !(p) 2493 * !(b)Time fields</b>: Pattern letters to output a time. 2494 * !(pre) 2495 * Pattern Count Equivalent builder methods 2496 * ------- ----- -------------------------- 2497 * a 1 appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT) 2498 * h 1 appendValue(ChronoField.CLOCK_HOUR_OF_AMPM) 2499 * hh 2 appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2) 2500 * H 1 appendValue(ChronoField.HOUR_OF_DAY) 2501 * HH 2 appendValue(ChronoField.HOUR_OF_DAY, 2) 2502 * k 1 appendValue(ChronoField.CLOCK_HOUR_OF_DAY) 2503 * kk 2 appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2) 2504 * K 1 appendValue(ChronoField.HOUR_OF_AMPM) 2505 * KK 2 appendValue(ChronoField.HOUR_OF_AMPM, 2) 2506 * m 1 appendValue(ChronoField.MINUTE_OF_HOUR) 2507 * mm 2 appendValue(ChronoField.MINUTE_OF_HOUR, 2) 2508 * s 1 appendValue(ChronoField.SECOND_OF_MINUTE) 2509 * ss 2 appendValue(ChronoField.SECOND_OF_MINUTE, 2) 2510 * 2511 * S..S 1..n appendFraction(ChronoField.NANO_OF_SECOND, n, n, false) 2512 * A..A 1..n appendValue(ChronoField.MILLI_OF_DAY, n, 19, SignStyle.NOT_NEGATIVE) 2513 * n..n 1..n appendValue(ChronoField.NANO_OF_SECOND, n, 19, SignStyle.NOT_NEGATIVE) 2514 * N..N 1..n appendValue(ChronoField.NANO_OF_DAY, n, 19, SignStyle.NOT_NEGATIVE) 2515 * </pre> 2516 * !(p) 2517 * !(b)Zone ID</b>: Pattern letters to output {@code ZoneId}. 2518 * !(pre) 2519 * Pattern Count Equivalent builder methods 2520 * ------- ----- -------------------------- 2521 * VV 2 appendZoneId() 2522 * v 1 appendGenericZoneText(TextStyle.SHORT) 2523 * vvvv 4 appendGenericZoneText(TextStyle.FULL) 2524 * z 1 appendZoneText(TextStyle.SHORT) 2525 * zz 2 appendZoneText(TextStyle.SHORT) 2526 * zzz 3 appendZoneText(TextStyle.SHORT) 2527 * zzzz 4 appendZoneText(TextStyle.FULL) 2528 * </pre> 2529 * !(p) 2530 * !(b)Zone offset</b>: Pattern letters to output {@code ZoneOffset}. 2531 * !(pre) 2532 * Pattern Count Equivalent builder methods 2533 * ------- ----- -------------------------- 2534 * O 1 appendLocalizedOffset(TextStyle.SHORT) 2535 * OOOO 4 appendLocalizedOffset(TextStyle.FULL) 2536 * X 1 appendOffset("+HHmm","Z") 2537 * XX 2 appendOffset("+HHMM","Z") 2538 * XXX 3 appendOffset("+HH:MM","Z") 2539 * XXXX 4 appendOffset("+HHMMss","Z") 2540 * XXXXX 5 appendOffset("+HH:MM:ss","Z") 2541 * x 1 appendOffset("+HHmm","+00") 2542 * xx 2 appendOffset("+HHMM","+0000") 2543 * xxx 3 appendOffset("+HH:MM","+00:00") 2544 * xxxx 4 appendOffset("+HHMMss","+0000") 2545 * xxxxx 5 appendOffset("+HH:MM:ss","+00:00") 2546 * Z 1 appendOffset("+HHMM","+0000") 2547 * ZZ 2 appendOffset("+HHMM","+0000") 2548 * ZZZ 3 appendOffset("+HHMM","+0000") 2549 * ZZZZ 4 appendLocalizedOffset(TextStyle.FULL) 2550 * ZZZZZ 5 appendOffset("+HH:MM:ss","Z") 2551 * </pre> 2552 * !(p) 2553 * !(b)Modifiers</b>: Pattern letters that modify the rest of the pattern: 2554 * !(pre) 2555 * Pattern Count Equivalent builder methods 2556 * ------- ----- -------------------------- 2557 * [ 1 optionalStart() 2558 * ] 1 optionalEnd() 2559 * p..p 1..n padNext(n) 2560 * </pre> 2561 * !(p) 2562 * Any sequence of letters not specified above, unrecognized letter or 2563 * reserved character will throw an exception. 2564 * Future versions may add to the set of patterns. 2565 * It is recommended to use single quotes around all characters that you want 2566 * to output directly to ensure that future changes do not break your application. 2567 * !(p) 2568 * Note that the pattern string is similar, but not identical, to 2569 * {@link java.text.SimpleDateFormat SimpleDateFormat}. 2570 * The pattern string is also similar, but not identical, to that defined by the 2571 * Unicode Common Locale Data Repository (CLDR/LDML). 2572 * Pattern letters 'X' and 'u' are aligned with Unicode CLDR/LDML. 2573 * By contrast, {@code SimpleDateFormat} uses 'u' for the numeric day of week. 2574 * Pattern letters 'y' and 'Y' parse years of two digits and more than 4 digits differently. 2575 * Pattern letters 'n', 'A', 'N', and 'p' are added. 2576 * Number types will reject large numbers. 2577 * 2578 * @param pattern the pattern to add, not null 2579 * @return this, for chaining, not null 2580 * @throws IllegalArgumentException if the pattern is invalid 2581 */ 2582 public DateTimeFormatterBuilder appendPattern(string pattern) 2583 { 2584 assert(pattern, "pattern"); 2585 parsePattern(pattern); 2586 return this; 2587 } 2588 2589 private void parsePattern(string pattern) 2590 { 2591 for (int pos = 0; pos < pattern.length; pos++) 2592 { 2593 char cur = pattern[pos]; 2594 if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) 2595 { 2596 int start = pos++; 2597 for (; pos < pattern.length && pattern[pos] == cur; pos++) 2598 { 2599 } // short loop 2600 int count = pos - start; 2601 // padding 2602 if (cur == 'p') 2603 { 2604 int pad = 0; 2605 if (pos < pattern.length) 2606 { 2607 cur = pattern[pos]; 2608 if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) 2609 { 2610 pad = count; 2611 start = pos++; 2612 for (; pos < pattern.length && pattern[pos] == cur; 2613 pos++) 2614 { 2615 } // short loop 2616 count = pos - start; 2617 } 2618 } 2619 if (pad == 0) 2620 { 2621 throw new IllegalArgumentException( 2622 "Pad letter 'p' must be followed by valid pad pattern: " ~ pattern); 2623 } 2624 padNext(pad); // pad and continue parsing 2625 } 2626 // main rules 2627 TemporalField field = FIELD_MAP.get(cur); 2628 if (field !is null) 2629 { 2630 parseField(cur, count, field); 2631 } 2632 else if (cur == 'z') 2633 { 2634 if (count > 4) 2635 { 2636 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2637 } 2638 else if (count == 4) 2639 { 2640 appendZoneText(TextStyle.FULL); 2641 } 2642 else 2643 { 2644 appendZoneText(TextStyle.SHORT); 2645 } 2646 } 2647 else if (cur == 'V') 2648 { 2649 if (count != 2) 2650 { 2651 throw new IllegalArgumentException("Pattern letter count must be 2: " ~ cur); 2652 } 2653 appendZoneId(); 2654 } 2655 else if (cur == 'v') 2656 { 2657 if (count == 1) 2658 { 2659 appendGenericZoneText(TextStyle.SHORT); 2660 } 2661 else if (count == 4) 2662 { 2663 appendGenericZoneText(TextStyle.FULL); 2664 } 2665 else 2666 { 2667 throw new IllegalArgumentException( 2668 "Wrong number of pattern letters: " ~ cur); 2669 } 2670 } 2671 else if (cur == 'Z') 2672 { 2673 if (count < 4) 2674 { 2675 appendOffset("+HHMM", "+0000"); 2676 } 2677 else if (count == 4) 2678 { 2679 appendLocalizedOffset(TextStyle.FULL); 2680 } 2681 else if (count == 5) 2682 { 2683 appendOffset("+HH:MM:ss", "Z"); 2684 } 2685 else 2686 { 2687 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2688 } 2689 } 2690 else if (cur == 'O') 2691 { 2692 if (count == 1) 2693 { 2694 appendLocalizedOffset(TextStyle.SHORT); 2695 } 2696 else if (count == 4) 2697 { 2698 appendLocalizedOffset(TextStyle.FULL); 2699 } 2700 else 2701 { 2702 throw new IllegalArgumentException( 2703 "Pattern letter count must be 1 or 4: " ~ cur); 2704 } 2705 } 2706 else if (cur == 'X') 2707 { 2708 if (count > 5) 2709 { 2710 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2711 } 2712 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z"); 2713 } 2714 else if (cur == 'x') 2715 { 2716 if (count > 5) 2717 { 2718 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2719 } 2720 string zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00")); 2721 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero); 2722 } 2723 else if (cur == 'W') 2724 { 2725 // Fields defined by Locale 2726 if (count > 1) 2727 { 2728 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2729 } 2730 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); 2731 } 2732 else if (cur == 'w') 2733 { 2734 // Fields defined by Locale 2735 if (count > 2) 2736 { 2737 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2738 } 2739 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2)); 2740 } 2741 else if (cur == 'Y') 2742 { 2743 // Fields defined by Locale 2744 if (count == 2) 2745 { 2746 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2)); 2747 } 2748 else 2749 { 2750 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 19)); 2751 } 2752 } 2753 else 2754 { 2755 throw new IllegalArgumentException("Unknown pattern letter: " ~ cur); 2756 } 2757 pos--; 2758 2759 } 2760 else if (cur == '\'') 2761 { 2762 // parse literals 2763 int start = pos++; 2764 for (; pos < pattern.length; pos++) 2765 { 2766 if (pattern[pos] == '\'') 2767 { 2768 if (pos + 1 < pattern.length && pattern[pos + 1] == '\'') 2769 { 2770 pos++; 2771 } 2772 else 2773 { 2774 break; // end of literal 2775 } 2776 } 2777 } 2778 if (pos >= pattern.length) 2779 { 2780 throw new IllegalArgumentException( 2781 "Pattern ends with an incomplete string literal: " ~ pattern); 2782 } 2783 string str = pattern.substring(start + 1, pos); 2784 if (str.length == 0) 2785 { 2786 appendLiteral('\''); 2787 } 2788 else 2789 { 2790 import std.array; 2791 2792 appendLiteral(str.replace("''", "'")); 2793 } 2794 2795 } 2796 else if (cur == '[') 2797 { 2798 optionalStart(); 2799 2800 } 2801 else if (cur == ']') 2802 { 2803 if (active.parent is null) 2804 { 2805 throw new IllegalArgumentException( 2806 "Pattern invalid as it contains ] without previous ["); 2807 } 2808 optionalEnd(); 2809 2810 } 2811 else if (cur == '{' || cur == '}' || cur == '#') 2812 { 2813 throw new IllegalArgumentException( 2814 "Pattern includes reserved character: '" ~ cur ~ "'"); 2815 } 2816 else 2817 { 2818 appendLiteral(cur); 2819 } 2820 } 2821 } 2822 2823 // @SuppressWarnings("fallthrough") 2824 private void parseField(char cur, int count, TemporalField field) 2825 { 2826 bool standalone = false; 2827 switch (cur) 2828 { 2829 case 'u': 2830 case 'y': 2831 if (count == 2) 2832 { 2833 appendValueReduced(field, 2, 2, ReducedPrinterParser.BASE_DATE); 2834 } 2835 else if (count < 4) 2836 { 2837 appendValue(field, count, 19, SignStyle.NORMAL); 2838 } 2839 else 2840 { 2841 appendValue(field, count, 19, SignStyle.EXCEEDS_PAD); 2842 } 2843 break; 2844 case 'c': 2845 if (count == 1) 2846 { 2847 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); 2848 break; 2849 } 2850 else if (count == 2) 2851 { 2852 throw new IllegalArgumentException("Invalid pattern \"cc\""); 2853 } 2854 /*fallthrough*/ 2855 goto case 'L'; 2856 case 'L': 2857 case 'q': 2858 standalone = true; 2859 /*fallthrough*/ 2860 goto case 'M'; 2861 case 'M': 2862 case 'Q': 2863 case 'E': 2864 case 'e': 2865 switch (count) 2866 { 2867 case 1: 2868 case 2: 2869 if (cur == 'e') 2870 { 2871 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); 2872 } 2873 else if (cur == 'E') 2874 { 2875 appendText(field, TextStyle.SHORT); 2876 } 2877 else 2878 { 2879 if (count == 1) 2880 { 2881 appendValue(field); 2882 } 2883 else 2884 { 2885 appendValue(field, 2); 2886 } 2887 } 2888 break; 2889 case 3: 2890 appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT); 2891 break; 2892 case 4: 2893 appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL); 2894 break; 2895 case 5: 2896 appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW); 2897 break; 2898 default: 2899 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2900 } 2901 break; 2902 case 'a': 2903 if (count == 1) 2904 { 2905 appendText(field, TextStyle.SHORT); 2906 } 2907 else 2908 { 2909 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2910 } 2911 break; 2912 case 'G': 2913 switch (count) 2914 { 2915 case 1: 2916 case 2: 2917 case 3: 2918 appendText(field, TextStyle.SHORT); 2919 break; 2920 case 4: 2921 appendText(field, TextStyle.FULL); 2922 break; 2923 case 5: 2924 appendText(field, TextStyle.NARROW); 2925 break; 2926 default: 2927 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2928 } 2929 break; 2930 case 'S': 2931 appendFraction(ChronoField.NANO_OF_SECOND, count, count, false); 2932 break; 2933 case 'F': 2934 if (count == 1) 2935 { 2936 appendValue(field); 2937 } 2938 else 2939 { 2940 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2941 } 2942 break; 2943 case 'd': 2944 case 'h': 2945 case 'H': 2946 case 'k': 2947 case 'K': 2948 case 'm': 2949 case 's': 2950 if (count == 1) 2951 { 2952 appendValue(field); 2953 } 2954 else if (count == 2) 2955 { 2956 appendValue(field, count); 2957 } 2958 else 2959 { 2960 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2961 } 2962 break; 2963 case 'D': 2964 if (count == 1) 2965 { 2966 appendValue(field); 2967 } 2968 else if (count == 2 || count == 3) 2969 { 2970 appendValue(field, count, 3, SignStyle.NOT_NEGATIVE); 2971 } 2972 else 2973 { 2974 throw new IllegalArgumentException("Too many pattern letters: " ~ cur); 2975 } 2976 break; 2977 case 'g': 2978 appendValue(field, count, 19, SignStyle.NORMAL); 2979 break; 2980 case 'A': 2981 case 'n': 2982 case 'N': 2983 appendValue(field, count, 19, SignStyle.NOT_NEGATIVE); 2984 break; 2985 default: 2986 if (count == 1) 2987 { 2988 appendValue(field); 2989 } 2990 else 2991 { 2992 appendValue(field, count); 2993 } 2994 break; 2995 } 2996 } 2997 2998 /** Map of letters to fields. */ 2999 // __gshared Map!(char, TemporalField) FIELD_MAP; 3000 3001 // shared static this() 3002 // { 3003 // FIELD_MAP = new HashMap!(char, TemporalField)(); 3004 mixin(MakeGlobalVar!(Map!(char, TemporalField))("FIELD_MAP",`new HashMap!(char, TemporalField)()`)); 3005 // } 3006 3007 static Comparator!(string) LENGTH_SORT; 3008 3009 // static this() 3010 // { 3011 // LENGTH_SORT = new class Comparator!(string) 3012 // { 3013 // override public int compare(string str1, string str2) 3014 // { 3015 // return str1.length == str2.length ? str1.compare(str2) 3016 // : cast(int)(str1.length - str2.length); 3017 // } 3018 // }; 3019 // // SDF = SimpleDateFormat 3020 // FIELD_MAP.put('G', ChronoField.ERA); // SDF, LDML (different to both for 1/2 chars) 3021 // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // SDF, LDML 3022 // FIELD_MAP.put('u', ChronoField.YEAR); // LDML (different _in SDF) 3023 // FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310) 3024 // FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR); // LDML (stand-alone) 3025 // FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // SDF, LDML 3026 // FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR); // SDF, LDML (stand-alone) 3027 // FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // SDF, LDML 3028 // FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // SDF, LDML 3029 // FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH); // SDF, LDML 3030 // FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // SDF, LDML (different to both for 1/2 chars) 3031 // FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK); // LDML (stand-alone) 3032 // FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK); // LDML (needs localized week number) 3033 // FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // SDF, LDML 3034 // FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // SDF, LDML 3035 // FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // SDF, LDML 3036 // FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // SDF, LDML 3037 // FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // SDF, LDML 3038 // FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // SDF, LDML 3039 // FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // SDF, LDML 3040 // FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (SDF uses milli-of-second number) 3041 // FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML 3042 // FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML) 3043 // FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML) 3044 // // FIELD_MAP.put('g', JulianFields.MODIFIED_JULIAN_DAY); 3045 // // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4 3046 // // 310 - Z - matches SimpleDateFormat and LDML 3047 // // 310 - V - time-zone id, matches LDML 3048 // // 310 - v - general timezone names, not matching exactly with LDML because LDML specify to fall back 3049 // // to 'VVVV' if general-nonlocation unavailable but here it's not falling back because of lack of data 3050 // // 310 - p - prefix for padding 3051 // // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5 3052 // // 310 - x - matches LDML 3053 // // 310 - w, W, and Y are localized forms matching LDML 3054 // // LDML - U - cycle year name, not supported by 310 yet 3055 // // LDML - l - deprecated 3056 // // LDML - j - not relevant 3057 // } 3058 3059 //----------------------------------------------------------------------- 3060 /** 3061 * Causes the next added printer/parser to pad to a fixed width using a space. 3062 * !(p) 3063 * This padding will pad to a fixed width using spaces. 3064 * !(p) 3065 * During formatting, the decorated element will be output and then padded 3066 * to the specified width. An exception will be thrown during formatting if 3067 * the pad width is exceeded. 3068 * !(p) 3069 * During parsing, the padding and decorated element are parsed. 3070 * If parsing is lenient, then the pad width is treated as a maximum. 3071 * The padding is parsed greedily. Thus, if the decorated element starts with 3072 * the pad character, it will not be parsed. 3073 * 3074 * @param padWidth the pad width, 1 or greater 3075 * @return this, for chaining, not null 3076 * @throws IllegalArgumentException if pad width is too small 3077 */ 3078 public DateTimeFormatterBuilder padNext(int padWidth) 3079 { 3080 return padNext(padWidth, ' '); 3081 } 3082 3083 /** 3084 * Causes the next added printer/parser to pad to a fixed width. 3085 * !(p) 3086 * This padding is intended for padding other than zero-padding. 3087 * Zero-padding should be achieved using the appendValue methods. 3088 * !(p) 3089 * During formatting, the decorated element will be output and then padded 3090 * to the specified width. An exception will be thrown during formatting if 3091 * the pad width is exceeded. 3092 * !(p) 3093 * During parsing, the padding and decorated element are parsed. 3094 * If parsing is lenient, then the pad width is treated as a maximum. 3095 * If parsing is case insensitive, then the pad character is matched ignoring case. 3096 * The padding is parsed greedily. Thus, if the decorated element starts with 3097 * the pad character, it will not be parsed. 3098 * 3099 * @param padWidth the pad width, 1 or greater 3100 * @param padChar the pad character 3101 * @return this, for chaining, not null 3102 * @throws IllegalArgumentException if pad width is too small 3103 */ 3104 public DateTimeFormatterBuilder padNext(int padWidth, char padChar) 3105 { 3106 if (padWidth < 1) 3107 { 3108 throw new IllegalArgumentException( 3109 "The pad width must be at least one but was " ~ padWidth.to!string); 3110 } 3111 active.padNextWidth = padWidth; 3112 active.padNextChar = padChar; 3113 active.valueParserIndex = -1; 3114 return this; 3115 } 3116 3117 //----------------------------------------------------------------------- 3118 /** 3119 * Mark the start of an optional section. 3120 * !(p) 3121 * The output of formatting can include optional sections, which may be nested. 3122 * An optional section is started by calling this method and ended by calling 3123 * {@link #optionalEnd()} or by ending the build process. 3124 * !(p) 3125 * All elements _in the optional section are treated as optional. 3126 * During formatting, the section is only output if data is available _in the 3127 * {@code TemporalAccessor} for all the elements _in the section. 3128 * During parsing, the whole section may be missing from the parsed string. 3129 * !(p) 3130 * For example, consider a builder setup as 3131 * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}. 3132 * The optional section ends automatically at the end of the builder. 3133 * During formatting, the minute will only be output if its value can be obtained from the date-time. 3134 * During parsing, the input will be successfully parsed whether the minute is present or not. 3135 * 3136 * @return this, for chaining, not null 3137 */ 3138 public DateTimeFormatterBuilder optionalStart() 3139 { 3140 active.valueParserIndex = -1; 3141 3142 _active = new DateTimeFormatterBuilder(active, true); 3143 // tmp.optional = true; 3144 // tmp.parent = this; 3145 // active = tmp; 3146 return this; 3147 } 3148 3149 /** 3150 * Ends an optional section. 3151 * !(p) 3152 * The output of formatting can include optional sections, which may be nested. 3153 * An optional section is started by calling {@link #optionalStart()} and ended 3154 * using this method (or at the end of the builder). 3155 * !(p) 3156 * Calling this method without having previously called {@code optionalStart} 3157 * will throw an exception. 3158 * Calling this method immediately after calling {@code optionalStart} has no effect 3159 * on the formatter other than ending the (empty) optional section. 3160 * !(p) 3161 * All elements _in the optional section are treated as optional. 3162 * During formatting, the section is only output if data is available _in the 3163 * {@code TemporalAccessor} for all the elements _in the section. 3164 * During parsing, the whole section may be missing from the parsed string. 3165 * !(p) 3166 * For example, consider a builder setup as 3167 * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}. 3168 * During formatting, the minute will only be output if its value can be obtained from the date-time. 3169 * During parsing, the input will be successfully parsed whether the minute is present or not. 3170 * 3171 * @return this, for chaining, not null 3172 * @throws IllegalStateException if there was no previous call to {@code optionalStart} 3173 */ 3174 public DateTimeFormatterBuilder optionalEnd() 3175 { 3176 if (active.parent is null) 3177 { 3178 throw new IllegalStateException( 3179 "Cannot call optionalEnd() as there was no previous call to optionalStart()"); 3180 } 3181 if (active.printerParsers.size() > 0) 3182 { 3183 CompositePrinterParser cpp = new CompositePrinterParser(active.printerParsers, 3184 active.optional); 3185 _active = active.parent; 3186 appendInternal(cpp); 3187 } 3188 else 3189 { 3190 _active = active.parent; 3191 } 3192 return this; 3193 } 3194 3195 //----------------------------------------------------------------------- 3196 /** 3197 * Appends a printer and/or parser to the internal list handling padding. 3198 * 3199 * @param pp the printer-parser to add, not null 3200 * @return the index into the active parsers list 3201 */ 3202 private int appendInternal(DateTimePrinterParser pp) 3203 { 3204 assert(pp, "pp"); 3205 if (active.padNextWidth > 0) 3206 { 3207 if (pp !is null) 3208 { 3209 pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar); 3210 } 3211 active.padNextWidth = 0; 3212 active.padNextChar = 0; 3213 } 3214 active.printerParsers.add(pp); 3215 active.valueParserIndex = -1; 3216 return active.printerParsers.size() - 1; 3217 } 3218 3219 //----------------------------------------------------------------------- 3220 /** 3221 * Completes this builder by creating the {@code DateTimeFormatter} 3222 * using the default locale. 3223 * !(p) 3224 * This will create a formatter with the {@linkplain Locale#getDefault(Locale.Category) default FORMAT locale}. 3225 * Numbers will be printed and parsed using the standard DecimalStyle. 3226 * The resolver style will be {@link ResolverStyle#SMART SMART}. 3227 * !(p) 3228 * Calling this method will end any open optional sections by repeatedly 3229 * calling {@link #optionalEnd()} before creating the formatter. 3230 * !(p) 3231 * This builder can still be used after creating the formatter if desired, 3232 * although the state may have been changed by calls to {@code optionalEnd}. 3233 * 3234 * @return the created formatter, not null 3235 */ 3236 public DateTimeFormatter toFormatter() 3237 { 3238 ///@gxc 3239 // return toFormatter(Locale.getDefault(Locale.Category.FORMAT)); 3240 return toFormatter(Locale.CHINESE); 3241 3242 // implementationMissing(); 3243 // return null; 3244 } 3245 3246 /** 3247 * Completes this builder by creating the {@code DateTimeFormatter} 3248 * using the specified locale. 3249 * !(p) 3250 * This will create a formatter with the specified locale. 3251 * Numbers will be printed and parsed using the standard DecimalStyle. 3252 * The resolver style will be {@link ResolverStyle#SMART SMART}. 3253 * !(p) 3254 * Calling this method will end any open optional sections by repeatedly 3255 * calling {@link #optionalEnd()} before creating the formatter. 3256 * !(p) 3257 * This builder can still be used after creating the formatter if desired, 3258 * although the state may have been changed by calls to {@code optionalEnd}. 3259 * 3260 * @param locale the locale to use for formatting, not null 3261 * @return the created formatter, not null 3262 */ 3263 public DateTimeFormatter toFormatter(Locale locale) 3264 { 3265 return toFormatter(locale, ResolverStyle.SMART, null); 3266 } 3267 3268 /** 3269 * Completes this builder by creating the formatter. 3270 * This uses the default locale. 3271 * 3272 * @param resolverStyle the resolver style to use, not null 3273 * @return the created formatter, not null 3274 */ 3275 DateTimeFormatter toFormatter(ResolverStyle resolverStyle, Chronology chrono) 3276 { 3277 //@gxc 3278 // return toFormatter(Locale.getDefault(Locale.Category.FORMAT), resolverStyle, chrono); 3279 return toFormatter(Locale.CHINESE, resolverStyle, chrono); 3280 3281 // implementationMissing(); 3282 // return null; 3283 } 3284 3285 /** 3286 * Completes this builder by creating the formatter. 3287 * 3288 * @param locale the locale to use for formatting, not null 3289 * @param chrono the chronology to use, may be null 3290 * @return the created formatter, not null 3291 */ 3292 private DateTimeFormatter toFormatter(Locale locale, 3293 ResolverStyle resolverStyle, Chronology chrono) 3294 { 3295 assert(locale, "locale"); 3296 while (active.parent !is null) 3297 { 3298 optionalEnd(); 3299 } 3300 CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false); 3301 return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD, 3302 resolverStyle, null, chrono, null); 3303 } 3304 3305 //------------------------------------------------------------------------- 3306 /** 3307 * Length comparator. 3308 */ 3309 3310 }