# HG changeset patch # User chegar # Date 1365886296 -3600 # Node ID 84df34924f674009d794317540fd74495cabc628 # Parent 5f46a666e06d321c77b6acef91db1e2fbe37111f# Parent 0111bab8dc357fdcc9b5ef89656f57ec70035b58 Merge diff -r 5f46a666e06d -r 84df34924f67 make/java/java/Makefile --- a/make/java/java/Makefile Sat Apr 13 20:16:00 2013 +0100 +++ b/make/java/java/Makefile Sat Apr 13 21:51:36 2013 +0100 @@ -313,6 +313,12 @@ CAL_PROPS = calendars.properties # +# Rule to copy Hijrah-umalqura calendar properties file. +# +HIJRAH_UMALQURA_PROPS = hijrah-config-umalqura.properties + + +# # Rule to copy tzmappings file on Windows # ifeq ($(PLATFORM), windows) @@ -324,7 +330,7 @@ $(call chmod-file, 444) endif -build: $(LIBDIR)/$(PROPS) $(LIBDIR)/$(CAL_PROPS) $(TZMAP) +build: $(LIBDIR)/$(PROPS) $(LIBDIR)/$(CAL_PROPS) $(LIBDIR)/$(HIJRAH_UMALQURA_PROPS) $(TZMAP) $(LIBDIR)/$(PROPS): $(PLATFORM_SRC)/lib/$(PROPS) $(install-file) @@ -332,6 +338,9 @@ $(LIBDIR)/$(CAL_PROPS): $(SHARE_SRC)/lib/$(CAL_PROPS) $(install-file) +$(LIBDIR)/$(HIJRAH_UMALQURA_PROPS): $(SHARE_SRC)/lib/$(HIJRAH_UMALQURA_PROPS) + $(install-file) + clean:: $(RM) -r $(LIBDIR)/$(PROPS) $(TZMAP) diff -r 5f46a666e06d -r 84df34924f67 make/java/java/mapfile-vers --- a/make/java/java/mapfile-vers Sat Apr 13 20:16:00 2013 +0100 +++ b/make/java/java/mapfile-vers Sat Apr 13 21:51:36 2013 +0100 @@ -217,7 +217,7 @@ Java_java_lang_Throwable_fillInStackTrace; Java_java_lang_Throwable_getStackTraceDepth; Java_java_lang_Throwable_getStackTraceElement; - Java_java_lang_UNIXProcess_initIDs; + Java_java_lang_UNIXProcess_init; Java_java_lang_UNIXProcess_waitForProcessExit; Java_java_lang_UNIXProcess_forkAndExec; Java_java_lang_UNIXProcess_destroyProcess; diff -r 5f46a666e06d -r 84df34924f67 make/java/text/base/FILES_java.gmk --- a/make/java/text/base/FILES_java.gmk Sat Apr 13 20:16:00 2013 +0100 +++ b/make/java/text/base/FILES_java.gmk Sat Apr 13 21:51:36 2013 +0100 @@ -1,5 +1,5 @@ # -# Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -105,5 +105,7 @@ sun/text/resources/CollationData.java \ \ sun/text/resources/FormatData.java \ + sun/text/resources/JavaTimeSupplementary.java \ sun/text/resources/en/FormatData_en.java \ - sun/text/resources/en/FormatData_en_US.java + sun/text/resources/en/FormatData_en_US.java \ + sun/text/resources/en/JavaTimeSupplementary_en.java \ diff -r 5f46a666e06d -r 84df34924f67 make/java/util/FILES_java.gmk --- a/make/java/util/FILES_java.gmk Sat Apr 13 20:16:00 2013 +0100 +++ b/make/java/util/FILES_java.gmk Sat Apr 13 21:51:36 2013 +0100 @@ -1,5 +1,5 @@ # -# Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ FILES_java = \ sun/util/resources/LocaleData.java \ sun/util/resources/OpenListResourceBundle.java \ + sun/util/resources/ParallelListResourceBundle.java \ sun/util/resources/LocaleNamesBundle.java \ sun/util/resources/TimeZoneNamesBundle.java \ sun/util/resources/TimeZoneNames.java \ diff -r 5f46a666e06d -r 84df34924f67 make/sun/text/FILES_java.gmk --- a/make/sun/text/FILES_java.gmk Sat Apr 13 20:16:00 2013 +0100 +++ b/make/sun/text/FILES_java.gmk Sat Apr 13 21:51:36 2013 +0100 @@ -1,5 +1,5 @@ # -# Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -227,5 +227,54 @@ sun/util/resources/sv/TimeZoneNames_sv.java \ sun/util/resources/zh/TimeZoneNames_zh_CN.java \ sun/util/resources/zh/TimeZoneNames_zh_TW.java \ - sun/util/resources/zh/TimeZoneNames_zh_HK.java + sun/util/resources/zh/TimeZoneNames_zh_HK.java \ + \ + sun/text/resources/ar/JavaTimeSupplementary_ar.java \ + sun/text/resources/be/JavaTimeSupplementary_be.java \ + sun/text/resources/bg/JavaTimeSupplementary_bg.java \ + sun/text/resources/ca/JavaTimeSupplementary_ca.java \ + sun/text/resources/cs/JavaTimeSupplementary_cs.java \ + sun/text/resources/da/JavaTimeSupplementary_da.java \ + sun/text/resources/de/JavaTimeSupplementary_de.java \ + sun/text/resources/el/JavaTimeSupplementary_el.java \ + sun/text/resources/en/JavaTimeSupplementary_en_GB.java \ + sun/text/resources/en/JavaTimeSupplementary_en_SG.java \ + sun/text/resources/es/JavaTimeSupplementary_es.java \ + sun/text/resources/et/JavaTimeSupplementary_et.java \ + sun/text/resources/fi/JavaTimeSupplementary_fi.java \ + sun/text/resources/fr/JavaTimeSupplementary_fr.java \ + sun/text/resources/ga/JavaTimeSupplementary_ga.java \ + sun/text/resources/hi/JavaTimeSupplementary_hi_IN.java \ + sun/text/resources/hr/JavaTimeSupplementary_hr.java \ + sun/text/resources/hu/JavaTimeSupplementary_hu.java \ + sun/text/resources/is/JavaTimeSupplementary_is.java \ + sun/text/resources/it/JavaTimeSupplementary_it.java \ + sun/text/resources/iw/JavaTimeSupplementary_iw.java \ + sun/text/resources/iw/JavaTimeSupplementary_iw_IL.java \ + sun/text/resources/ja/JavaTimeSupplementary_ja.java \ + sun/text/resources/ko/JavaTimeSupplementary_ko.java \ + sun/text/resources/lt/JavaTimeSupplementary_lt.java \ + sun/text/resources/lv/JavaTimeSupplementary_lv.java \ + sun/text/resources/mk/JavaTimeSupplementary_mk.java \ + sun/text/resources/ms/JavaTimeSupplementary_ms.java \ + sun/text/resources/mt/JavaTimeSupplementary_mt.java \ + sun/text/resources/nl/JavaTimeSupplementary_nl.java \ + sun/text/resources/no/JavaTimeSupplementary_no.java \ + sun/text/resources/pl/JavaTimeSupplementary_pl.java \ + sun/text/resources/pt/JavaTimeSupplementary_pt.java \ + sun/text/resources/pt/JavaTimeSupplementary_pt_PT.java \ + sun/text/resources/ro/JavaTimeSupplementary_ro.java \ + sun/text/resources/ru/JavaTimeSupplementary_ru.java \ + sun/text/resources/sk/JavaTimeSupplementary_sk.java \ + sun/text/resources/sl/JavaTimeSupplementary_sl.java \ + sun/text/resources/sq/JavaTimeSupplementary_sq.java \ + sun/text/resources/sr/JavaTimeSupplementary_sr.java \ + sun/text/resources/sr/JavaTimeSupplementary_sr_Latn.java \ + sun/text/resources/sv/JavaTimeSupplementary_sv.java \ + sun/text/resources/th/JavaTimeSupplementary_th.java \ + sun/text/resources/tr/JavaTimeSupplementary_tr.java \ + sun/text/resources/uk/JavaTimeSupplementary_uk.java \ + sun/text/resources/vi/JavaTimeSupplementary_vi.java \ + sun/text/resources/zh/JavaTimeSupplementary_zh.java \ + sun/text/resources/zh/JavaTimeSupplementary_zh_TW.java diff -r 5f46a666e06d -r 84df34924f67 make/sun/tzdb/Makefile --- a/make/sun/tzdb/Makefile Sat Apr 13 20:16:00 2013 +0100 +++ b/make/sun/tzdb/Makefile Sat Apr 13 21:51:36 2013 +0100 @@ -42,7 +42,6 @@ # Time zone data file creation # TZDATA_DIR := ../javazic/tzdata -TZDATA_VER := $(subst tzdata,,$(shell $(GREP) '^tzdata' $(TZDATA_DIR)/VERSION)) TZFILE := \ africa antarctica asia australasia europe northamerica \ pacificnew southamerica backward etcetera \ @@ -50,9 +49,7 @@ TZFILES := $(addprefix $(TZDATA_DIR)/,$(TZFILE)) - - -TZDB_JAR = tzdb.jar +TZDB_DAT = $(LIBDIR)/tzdb.dat # # Rules @@ -62,13 +59,12 @@ # # Add to the build rule # -build: $(LIBDIR)/$(TZDB_JAR) +build: $(TZDB_DAT) -$(LIBDIR)/$(TZDB_JAR): $(TZFILES) +$(TZDB_DAT): $(TZFILES) $(prep-target) - echo build tzdb from version $(TZDATA_VER) $(BOOT_JAVA_CMD) -jar $(BUILDTOOLJARDIR)/tzdb.jar \ - -version $(TZDATA_VER) -srcdir $(TZDATA_DIR) -dstdir $(LIBDIR) $(TZFILE) + -srcdir $(TZDATA_DIR) -dstfile $(TZDB_DAT) $(TZFILE) clean clobber:: - $(RM) $(LIBDIR)/$(TZDB_JAR) + $(RM) $(TZDB_DAT) diff -r 5f46a666e06d -r 84df34924f67 make/tools/src/build/tools/cldrconverter/AbstractLDMLHandler.java --- a/make/tools/src/build/tools/cldrconverter/AbstractLDMLHandler.java Sat Apr 13 20:16:00 2013 +0100 +++ b/make/tools/src/build/tools/cldrconverter/AbstractLDMLHandler.java Sat Apr 13 21:51:36 2013 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package build.tools.cldrconverter; +import build.tools.cldrconverter.CLDRConverter.DraftType; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -88,7 +89,7 @@ } String draftValue = attributes.getValue("draft"); if (draftValue != null) { - return CLDRConverter.draftType > CLDRConverter.DRAFT_MAP.get(draftValue); + return DraftType.getDefault().ordinal() > DraftType.forKeyword(draftValue).ordinal(); } return false; } diff -r 5f46a666e06d -r 84df34924f67 make/tools/src/build/tools/cldrconverter/Bundle.java --- a/make/tools/src/build/tools/cldrconverter/Bundle.java Sat Apr 13 20:16:00 2013 +0100 +++ b/make/tools/src/build/tools/cldrconverter/Bundle.java Sat Apr 13 21:51:36 2013 +0100 @@ -266,6 +266,9 @@ handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "DayNarrows"); handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "AmPmMarkers"); handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "narrow.AmPmMarkers"); + handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterNames"); + handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterAbbreviations"); + handleMultipleInheritance(myMap, parentsMap, calendarPrefix + "QuarterNarrows"); adjustEraNames(myMap, calendarType); @@ -484,25 +487,33 @@ for (String k : patternKeys) { if (myMap.containsKey(calendarPrefix + k)) { int len = patternKeys.length; - List rawPatterns = new ArrayList<>(); - List patterns = new ArrayList<>(); + List rawPatterns = new ArrayList<>(len); + List patterns = new ArrayList<>(len); for (int i = 0; i < len; i++) { String key = calendarPrefix + patternKeys[i]; String pattern = (String) myMap.remove(key); if (pattern == null) { pattern = (String) parentsMap.remove(key); } + rawPatterns.add(i, pattern); if (pattern != null) { - rawPatterns.add(i, pattern); patterns.add(i, translateDateFormatLetters(calendarType, pattern)); + } else { + patterns.add(i, null); } } + // If patterns is empty or has any nulls, discard patterns. if (patterns.isEmpty()) { return; } + for (String p : patterns) { + if (p == null) { + return; + } + } String key = calendarPrefix + name; if (!rawPatterns.equals(patterns)) { - myMap.put("cldr." + key, rawPatterns.toArray(new String[len])); + myMap.put("java.time." + key, rawPatterns.toArray(new String[len])); } myMap.put(key, patterns.toArray(new String[len])); break; diff -r 5f46a666e06d -r 84df34924f67 make/tools/src/build/tools/cldrconverter/CLDRConverter.java --- a/make/tools/src/build/tools/cldrconverter/CLDRConverter.java Sat Apr 13 20:16:00 2013 +0100 +++ b/make/tools/src/build/tools/cldrconverter/CLDRConverter.java Sat Apr 13 21:51:36 2013 +0100 @@ -68,25 +68,43 @@ static MetaZonesParseHandler handlerMetaZones; private static BundleGenerator bundleGenerator; - static int draftType; - private static final String DRAFT_UNCONFIRMED = "unconfirmed"; - private static final String DRAFT_PROVISIONAL = "provisional"; - private static final String DRAFT_CONTRIBUTED = "contributed"; - private static final String DRAFT_APPROVED = "approved"; - private static final String DRAFT_TRUE = "true"; - private static final String DRAFT_FALSE = "false"; - private static final String DRAFT_DEFAULT = DRAFT_APPROVED; - static final Map DRAFT_MAP = new HashMap<>(); + static enum DraftType { + UNCONFIRMED, + PROVISIONAL, + CONTRIBUTED, + APPROVED; + + private static final Map map = new HashMap<>(); + static { + for (DraftType dt : values()) { + map.put(dt.getKeyword(), dt); + } + } + static private DraftType defaultType = CONTRIBUTED; + + private final String keyword; + + private DraftType() { + keyword = this.name().toLowerCase(Locale.ROOT); - static { - DRAFT_MAP.put(DRAFT_UNCONFIRMED, 0); - DRAFT_MAP.put(DRAFT_PROVISIONAL, 1); - DRAFT_MAP.put(DRAFT_CONTRIBUTED, 2); - DRAFT_MAP.put(DRAFT_APPROVED, 3); - DRAFT_MAP.put(DRAFT_TRUE, 0); - DRAFT_MAP.put(DRAFT_FALSE, 2); - draftType = DRAFT_MAP.get(DRAFT_DEFAULT); - }; + } + + static DraftType forKeyword(String keyword) { + return map.get(keyword); + } + + static DraftType getDefault() { + return defaultType; + } + + static void setDefault(String keyword) { + defaultType = Objects.requireNonNull(forKeyword(keyword)); + } + + String getKeyword() { + return keyword; + } + } static boolean USE_UTF8 = false; private static boolean verbose; @@ -106,7 +124,7 @@ case "-draft": String draftDataType = args[++i]; try { - draftType = DRAFT_MAP.get(draftDataType); + DraftType.setDefault(draftDataType); } catch (NullPointerException e) { severe("Error: incorrect draft value: %s%n", draftDataType); System.exit(1); @@ -525,7 +543,7 @@ "standalone.MonthNames", "MonthAbbreviations", "standalone.MonthAbbreviations", - "MonthNarrow", + "MonthNarrows", "standalone.MonthNarrows", "DayNames", "standalone.DayNames", @@ -533,6 +551,12 @@ "standalone.DayAbbreviations", "DayNarrows", "standalone.DayNarrows", + "QuarterNames", + "standalone.QuarterNames", + "QuarterAbbreviations", + "standalone.QuarterAbbreviations", + "QuarterNarrows", + "standalone.QuarterNarrows", "AmPmMarkers", "narrow.AmPmMarkers", "long.Eras", @@ -560,7 +584,7 @@ String prefix = calendarType.keyElementName(); for (String element : FORMAT_DATA_ELEMENTS) { String key = prefix + element; - copyIfPresent(map, "cldr." + key, formatData); + copyIfPresent(map, "java.time." + key, formatData); copyIfPresent(map, key, formatData); } } diff -r 5f46a666e06d -r 84df34924f67 make/tools/src/build/tools/cldrconverter/LDMLParseHandler.java --- a/make/tools/src/build/tools/cldrconverter/LDMLParseHandler.java Sat Apr 13 20:16:00 2013 +0100 +++ b/make/tools/src/build/tools/cldrconverter/LDMLParseHandler.java Sat Apr 13 21:51:36 2013 +0100 @@ -356,6 +356,44 @@ } } break; + case "quarterContext": + { + // for FormatData + // need to keep stand-alone and format, to allow for inheritance in CLDR + String type = attributes.getValue("type"); + if ("stand-alone".equals(type) || "format".equals(type)) { + pushKeyContainer(qName, attributes, type); + } else { + pushIgnoredContainer(qName); + } + } + break; + case "quarterWidth": + { + // for FormatData + // keep info about the context type so we can sort out inheritance later + String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName(); + switch (attributes.getValue("type")) { + case "wide": + pushStringArrayEntry(qName, attributes, prefix + "QuarterNames/" + getContainerKey(), 4); + break; + case "abbreviated": + pushStringArrayEntry(qName, attributes, prefix + "QuarterAbbreviations/" + getContainerKey(), 4); + break; + case "narrow": + pushStringArrayEntry(qName, attributes, prefix + "QuarterNarrows/" + getContainerKey(), 4); + break; + default: + pushIgnoredContainer(qName); + break; + } + } + break; + case "quarter": + // for FormatData + // add to string array entry of quarterWidth element + pushStringArrayElement(qName, attributes, Integer.parseInt(attributes.getValue("type")) - 1); + break; // // Time zone names diff -r 5f46a666e06d -r 84df34924f67 make/tools/src/build/tools/tzdb/TzdbZoneRulesCompiler.java --- a/make/tools/src/build/tools/tzdb/TzdbZoneRulesCompiler.java Sat Apr 13 20:16:00 2013 +0100 +++ b/make/tools/src/build/tools/tzdb/TzdbZoneRulesCompiler.java Sat Apr 13 21:51:36 2013 +0100 @@ -58,12 +58,12 @@ import static build.tools.tzdb.Utils.*; -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Arrays; @@ -71,132 +71,131 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.NoSuchElementException; +import java.util.Scanner; import java.util.SortedMap; -import java.util.StringTokenizer; import java.util.TreeMap; -import java.util.TreeSet; -import java.util.jar.JarOutputStream; -import java.util.zip.ZipEntry; import java.util.regex.Matcher; +import java.util.regex.MatchResult; import java.util.regex.Pattern; /** - * A builder that can read the TZDB time-zone files and build {@code ZoneRules} instances. + * A compiler that reads a set of TZDB time-zone files and builds a single + * combined TZDB data file. * * @since 1.8 */ public final class TzdbZoneRulesCompiler { - private static final Matcher YEAR = Pattern.compile("(?i)(?min)|(?max)|(?only)|(?[0-9]+)").matcher(""); - private static final Matcher MONTH = Pattern.compile("(?i)(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)").matcher(""); - private static final Matcher DOW = Pattern.compile("(?i)(mon)|(tue)|(wed)|(thu)|(fri)|(sat)|(sun)").matcher(""); - private static final Matcher TIME = Pattern.compile("(?-)?+(?[0-9]{1,2})(:(?[0-5][0-9]))?+(:(?[0-5][0-9]))?+").matcher(""); + public static void main(String[] args) { + new TzdbZoneRulesCompiler().compile(args); + } - /** - * Constant for MJD 1972-01-01. - */ - private static final long MJD_1972_01_01 = 41317L; - - /** - * Reads a set of TZDB files and builds a single combined data file. - * - * @param args the arguments - */ - public static void main(String[] args) { + private void compile(String[] args) { if (args.length < 2) { outputHelp(); return; } - - // parse args + Path srcDir = null; + Path dstFile = null; String version = null; - File baseSrcDir = null; - File dstDir = null; - boolean verbose = false; - - // parse options + // parse args/options int i; for (i = 0; i < args.length; i++) { String arg = args[i]; - if (arg.startsWith("-") == false) { + if (!arg.startsWith("-")) { break; } if ("-srcdir".equals(arg)) { - if (baseSrcDir == null && ++i < args.length) { - baseSrcDir = new File(args[i]); + if (srcDir == null && ++i < args.length) { + srcDir = Paths.get(args[i]); continue; } - } else if ("-dstdir".equals(arg)) { - if (dstDir == null && ++i < args.length) { - dstDir = new File(args[i]); - continue; - } - } else if ("-version".equals(arg)) { - if (version == null && ++i < args.length) { - version = args[i]; + } else if ("-dstfile".equals(arg)) { + if (dstFile == null && ++i < args.length) { + dstFile = Paths.get(args[i]); continue; } } else if ("-verbose".equals(arg)) { - if (verbose == false) { + if (!verbose) { verbose = true; continue; } - } else if ("-help".equals(arg) == false) { + } else if (!"-help".equals(arg)) { System.out.println("Unrecognised option: " + arg); } outputHelp(); return; } - // check source directory - if (baseSrcDir == null) { - System.out.println("Source directory must be specified using -srcdir: " + baseSrcDir); - return; + if (srcDir == null) { + System.err.println("Source directory must be specified using -srcdir"); + System.exit(1); } - if (baseSrcDir.isDirectory() == false) { - System.out.println("Source does not exist or is not a directory: " + baseSrcDir); - return; + if (!Files.isDirectory(srcDir)) { + System.err.println("Source does not exist or is not a directory: " + srcDir); + System.exit(1); } - dstDir = (dstDir != null ? dstDir : baseSrcDir); - // parse source file names - List srcFileNames = Arrays.asList(Arrays.copyOfRange(args, i, args.length)); - if (srcFileNames.isEmpty()) { - System.out.println("Source filenames not specified, using default set"); - System.out.println("(africa antarctica asia australasia backward etcetera europe northamerica southamerica)"); - srcFileNames = Arrays.asList("africa", "antarctica", "asia", "australasia", "backward", - "etcetera", "europe", "northamerica", "southamerica"); + if (i == args.length) { + i = 0; + args = new String[] {"africa", "antarctica", "asia", "australasia", "europe", + "northamerica","southamerica", "backward", "etcetera" }; + System.out.println("Source filenames not specified, using default set ( "); + for (String name : args) { + System.out.printf(name + " "); + } + System.out.println(")"); } - - // find source directories to process - List srcDirs = new ArrayList<>(); - if (version != null) { - // if the "version" specified, as in jdk repo, the "baseSrcDir" is - // the "srcDir" that contains the tzdb data. - srcDirs.add(baseSrcDir); - } else { - File[] dirs = baseSrcDir.listFiles(); - for (File dir : dirs) { - if (dir.isDirectory() && dir.getName().matches("[12][0-9]{3}[A-Za-z0-9._-]+")) { - srcDirs.add(dir); - } + // source files in this directory + List srcFiles = new ArrayList<>(); + for (; i < args.length; i++) { + Path file = srcDir.resolve(args[i]); + if (Files.exists(file)) { + srcFiles.add(file); + } else { + System.err.println("Source directory does not contain source file: " + args[i]); + System.exit(1); } } - if (srcDirs.isEmpty()) { - System.out.println("Source directory contains no valid source folders: " + baseSrcDir); - return; + // check destination file + if (dstFile == null) { + dstFile = srcDir.resolve("tzdb.dat"); + } else { + Path parent = dstFile.getParent(); + if (parent != null && !Files.exists(parent)) { + System.err.println("Destination directory does not exist: " + parent); + System.exit(1); + } } - // check destination directory - if (dstDir.exists() == false && dstDir.mkdirs() == false) { - System.out.println("Destination directory could not be created: " + dstDir); - return; + try { + // get tzdb source version + Matcher m = Pattern.compile("tzdata(?[0-9]{4}[A-z])") + .matcher(new String(Files.readAllBytes(srcDir.resolve("VERSION")), + "ISO-8859-1")); + if (m.find()) { + version = m.group("ver"); + } else { + System.exit(1); + System.err.println("Source directory does not contain file: VERSION"); + } + printVerbose("Compiling TZDB version " + version); + // parse source files + for (Path file : srcFiles) { + printVerbose("Parsing file: " + file); + parseFile(file); + } + // build zone rules + printVerbose("Building rules"); + buildZoneRules(); + // output to file + printVerbose("Outputting tzdb file: " + dstFile); + outputFile(dstFile, version, builtZones, links); + } catch (Exception ex) { + System.out.println("Failed: " + ex.toString()); + ex.printStackTrace(); + System.exit(1); } - if (dstDir.isDirectory() == false) { - System.out.println("Destination is not a directory: " + dstDir); - return; - } - process(srcDirs, srcFileNames, dstDir, version, verbose); System.exit(0); } @@ -206,145 +205,35 @@ private static void outputHelp() { System.out.println("Usage: TzdbZoneRulesCompiler "); System.out.println("where options include:"); - System.out.println(" -srcdir Where to find source directories (required)"); - System.out.println(" -dstdir Where to output generated files (default srcdir)"); - System.out.println(" -version Specify the version, such as 2009a (optional)"); + System.out.println(" -srcdir Where to find tzdb source directory (required)"); + System.out.println(" -dstfile Where to output generated file (default srcdir/tzdb.dat)"); System.out.println(" -help Print this usage message"); System.out.println(" -verbose Output verbose information during compilation"); - System.out.println(" There must be one directory for each version in srcdir"); - System.out.println(" Each directory must have the name of the version, such as 2009a"); - System.out.println(" Each directory must contain the unpacked tzdb files, such as asia or europe"); - System.out.println(" Directories must match the regex [12][0-9][0-9][0-9][A-Za-z0-9._-]+"); - System.out.println(" There will be one jar file for each version and one combined jar in dstdir"); - System.out.println(" If the version is specified, only that version is processed"); - } - - /** - * Process to create the jar files. - */ - private static void process(List srcDirs, List srcFileNames, File dstDir, String version, boolean verbose) { - // build actual jar files - Map> allBuiltZones = new TreeMap<>(); - Set allRegionIds = new TreeSet(); - Set allRules = new HashSet(); - Map> allLinks = new TreeMap<>(); - - for (File srcDir : srcDirs) { - // source files in this directory - List srcFiles = new ArrayList<>(); - for (String srcFileName : srcFileNames) { - File file = new File(srcDir, srcFileName); - if (file.exists()) { - srcFiles.add(file); - } - } - if (srcFiles.isEmpty()) { - continue; // nothing to process - } - - // compile - String loopVersion = (srcDirs.size() == 1 && version != null) - ? version : srcDir.getName(); - TzdbZoneRulesCompiler compiler = new TzdbZoneRulesCompiler(loopVersion, srcFiles, verbose); - try { - // compile - compiler.compile(); - SortedMap builtZones = compiler.getZones(); - - // output version-specific file - File dstFile = version == null ? new File(dstDir, "tzdb" + loopVersion + ".jar") - : new File(dstDir, "tzdb.jar"); - if (verbose) { - System.out.println("Outputting file: " + dstFile); - } - outputFile(dstFile, loopVersion, builtZones, compiler.links); - - // create totals - allBuiltZones.put(loopVersion, builtZones); - allRegionIds.addAll(builtZones.keySet()); - allRules.addAll(builtZones.values()); - allLinks.put(loopVersion, compiler.links); - } catch (Exception ex) { - System.out.println("Failed: " + ex.toString()); - ex.printStackTrace(); - System.exit(1); - } - } - - // output merged file - if (version == null) { - File dstFile = new File(dstDir, "tzdb-all.jar"); - if (verbose) { - System.out.println("Outputting combined file: " + dstFile); - } - outputFile(dstFile, allBuiltZones, allRegionIds, allRules, allLinks); - } + System.out.println(" The source directory must contain the unpacked tzdb files, such as asia or europe"); } /** * Outputs the file. */ - private static void outputFile(File dstFile, - String version, - SortedMap builtZones, - Map links) { - Map> loopAllBuiltZones = new TreeMap<>(); - loopAllBuiltZones.put(version, builtZones); - Set loopAllRegionIds = new TreeSet(builtZones.keySet()); - Set loopAllRules = new HashSet(builtZones.values()); - Map> loopAllLinks = new TreeMap<>(); - loopAllLinks.put(version, links); - outputFile(dstFile, loopAllBuiltZones, loopAllRegionIds, loopAllRules, loopAllLinks); - } - - /** - * Outputs the file. - */ - private static void outputFile(File dstFile, - Map> allBuiltZones, - Set allRegionIds, - Set allRules, - Map> allLinks) { - try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(dstFile))) { - outputTZEntry(jos, allBuiltZones, allRegionIds, allRules, allLinks); - } catch (Exception ex) { - System.out.println("Failed: " + ex.toString()); - ex.printStackTrace(); - System.exit(1); - } - } - - /** - * Outputs the timezone entry in the JAR file. - */ - private static void outputTZEntry(JarOutputStream jos, - Map> allBuiltZones, - Set allRegionIds, - Set allRules, - Map> allLinks) { - // this format is not publicly specified - try { - jos.putNextEntry(new ZipEntry("TZDB.dat")); - DataOutputStream out = new DataOutputStream(jos); - + private void outputFile(Path dstFile, String version, + SortedMap builtZones, + Map links) { + try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(dstFile))) { // file version out.writeByte(1); // group out.writeUTF("TZDB"); // versions - String[] versionArray = allBuiltZones.keySet().toArray(new String[allBuiltZones.size()]); - out.writeShort(versionArray.length); - for (String version : versionArray) { - out.writeUTF(version); - } + out.writeShort(1); + out.writeUTF(version); // regions - String[] regionArray = allRegionIds.toArray(new String[allRegionIds.size()]); + String[] regionArray = builtZones.keySet().toArray(new String[builtZones.size()]); out.writeShort(regionArray.length); for (String regionId : regionArray) { out.writeUTF(regionId); } - // rules - List rulesList = new ArrayList<>(allRules); + // rules -- hashset -> remove the dup + List rulesList = new ArrayList<>(new HashSet<>(builtZones.values())); out.writeShort(rulesList.size()); ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); for (ZoneRules rules : rulesList) { @@ -357,27 +246,22 @@ out.write(bytes); } // link version-region-rules - for (String version : allBuiltZones.keySet()) { - out.writeShort(allBuiltZones.get(version).size()); - for (Map.Entry entry : allBuiltZones.get(version).entrySet()) { - int regionIndex = Arrays.binarySearch(regionArray, entry.getKey()); - int rulesIndex = rulesList.indexOf(entry.getValue()); - out.writeShort(regionIndex); - out.writeShort(rulesIndex); - } + out.writeShort(builtZones.size()); + for (Map.Entry entry : builtZones.entrySet()) { + int regionIndex = Arrays.binarySearch(regionArray, entry.getKey()); + int rulesIndex = rulesList.indexOf(entry.getValue()); + out.writeShort(regionIndex); + out.writeShort(rulesIndex); } // alias-region - for (String version : allLinks.keySet()) { - out.writeShort(allLinks.get(version).size()); - for (Map.Entry entry : allLinks.get(version).entrySet()) { - int aliasIndex = Arrays.binarySearch(regionArray, entry.getKey()); - int regionIndex = Arrays.binarySearch(regionArray, entry.getValue()); - out.writeShort(aliasIndex); - out.writeShort(regionIndex); - } + out.writeShort(links.size()); + for (Map.Entry entry : links.entrySet()) { + int aliasIndex = Arrays.binarySearch(regionArray, entry.getKey()); + int regionIndex = Arrays.binarySearch(regionArray, entry.getValue()); + out.writeShort(aliasIndex); + out.writeShort(regionIndex); } out.flush(); - jos.closeEntry(); } catch (Exception ex) { System.out.println("Failed: " + ex.toString()); ex.printStackTrace(); @@ -385,76 +269,30 @@ } } - //----------------------------------------------------------------------- + private static final Pattern YEAR = Pattern.compile("(?i)(?min)|(?max)|(?only)|(?[0-9]+)"); + private static final Pattern MONTH = Pattern.compile("(?i)(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)"); + private static final Matcher DOW = Pattern.compile("(?i)(mon)|(tue)|(wed)|(thu)|(fri)|(sat)|(sun)").matcher(""); + private static final Matcher TIME = Pattern.compile("(?-)?+(?[0-9]{1,2})(:(?[0-5][0-9]))?+(:(?[0-5][0-9]))?+").matcher(""); + /** The TZDB rules. */ private final Map> rules = new HashMap<>(); /** The TZDB zones. */ private final Map> zones = new HashMap<>(); + /** The TZDB links. */ - private final Map links = new HashMap<>(); /** The built zones. */ private final SortedMap builtZones = new TreeMap<>(); - - /** The version to produce. */ - private final String version; - - /** The source files. */ - - private final List sourceFiles; - - /** The version to produce. */ - private final boolean verbose; - - /** - * Creates an instance if you want to invoke the compiler manually. - * - * @param version the version, such as 2009a, not null - * @param sourceFiles the list of source files, not empty, not null - * @param verbose whether to output verbose messages - */ - public TzdbZoneRulesCompiler(String version, List sourceFiles, boolean verbose) { - this.version = version; - this.sourceFiles = sourceFiles; - this.verbose = verbose; - } + /** Whether to output verbose messages. */ + private boolean verbose; /** - * Compile the rules file. - *

- * Use {@link #getZones()} to retrieve the parsed data. - * - * @throws Exception if an error occurs + * private contructor */ - public void compile() throws Exception { - printVerbose("Compiling TZDB version " + version); - parseFiles(); - buildZoneRules(); - printVerbose("Compiled TZDB version " + version); - } - - /** - * Gets the parsed zone rules. - * - * @return the parsed zone rules, not null - */ - public SortedMap getZones() { - return builtZones; - } - - /** - * Parses the source files. - * - * @throws Exception if an error occurs - */ - private void parseFiles() throws Exception { - for (File file : sourceFiles) { - printVerbose("Parsing file: " + file); - parseFile(file); - } + private TzdbZoneRulesCompiler() { } /** @@ -463,14 +301,14 @@ * @param file the file being read, not null * @throws Exception if an error occurs */ - private void parseFile(File file) throws Exception { + private void parseFile(Path file) throws Exception { int lineNumber = 1; String line = null; - BufferedReader in = null; try { - in = new BufferedReader(new FileReader(file)); + List lines = Files.readAllLines(file, StandardCharsets.ISO_8859_1); List openZone = null; - for ( ; (line = in.readLine()) != null; lineNumber++) { + for (; lineNumber < lines.size(); lineNumber++) { + line = lines.get(lineNumber); int index = line.indexOf('#'); // remove comments (doesn't handle # in quotes) if (index >= 0) { line = line.substring(0, index); @@ -478,41 +316,43 @@ if (line.trim().length() == 0) { // ignore blank lines continue; } - StringTokenizer st = new StringTokenizer(line, " \t"); - if (openZone != null && Character.isWhitespace(line.charAt(0)) && st.hasMoreTokens()) { - if (parseZoneLine(st, openZone)) { + Scanner s = new Scanner(line); + if (openZone != null && Character.isWhitespace(line.charAt(0)) && s.hasNext()) { + if (parseZoneLine(s, openZone)) { openZone = null; } } else { - if (st.hasMoreTokens()) { - String first = st.nextToken(); + if (s.hasNext()) { + String first = s.next(); if (first.equals("Zone")) { - if (st.countTokens() < 3) { + openZone = new ArrayList<>(); + try { + zones.put(s.next(), openZone); + if (parseZoneLine(s, openZone)) { + openZone = null; + } + } catch (NoSuchElementException x) { printVerbose("Invalid Zone line in file: " + file + ", line: " + line); throw new IllegalArgumentException("Invalid Zone line"); } - openZone = new ArrayList<>(); - zones.put(st.nextToken(), openZone); - if (parseZoneLine(st, openZone)) { - openZone = null; - } } else { openZone = null; if (first.equals("Rule")) { - if (st.countTokens() < 9) { + try { + parseRuleLine(s); + } catch (NoSuchElementException x) { printVerbose("Invalid Rule line in file: " + file + ", line: " + line); throw new IllegalArgumentException("Invalid Rule line"); } - parseRuleLine(st); - } else if (first.equals("Link")) { - if (st.countTokens() < 2) { + try { + String realId = s.next(); + String aliasId = s.next(); + links.put(aliasId, realId); + } catch (NoSuchElementException x) { printVerbose("Invalid Link line in file: " + file + ", line: " + line); throw new IllegalArgumentException("Invalid Link line"); } - String realId = st.nextToken(); - String aliasId = st.nextToken(); - links.put(aliasId, realId); } else { throw new IllegalArgumentException("Unknown line"); @@ -522,52 +362,44 @@ } } } catch (Exception ex) { - throw new Exception("Failed while processing file '" + file + "' on line " + lineNumber + " '" + line + "'", ex); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (Exception ex) { - // ignore NPE and IOE - } + throw new Exception("Failed while parsing file '" + file + "' on line " + lineNumber + " '" + line + "'", ex); } } /** * Parses a Rule line. * - * @param st the tokenizer, not null + * @param s the line scanner, not null */ - private void parseRuleLine(StringTokenizer st) { + private void parseRuleLine(Scanner s) { TZDBRule rule = new TZDBRule(); - String name = st.nextToken(); + String name = s.next(); if (rules.containsKey(name) == false) { rules.put(name, new ArrayList()); } rules.get(name).add(rule); - rule.startYear = parseYear(st.nextToken(), 0); - rule.endYear = parseYear(st.nextToken(), rule.startYear); + rule.startYear = parseYear(s, 0); + rule.endYear = parseYear(s, rule.startYear); if (rule.startYear > rule.endYear) { throw new IllegalArgumentException("Year order invalid: " + rule.startYear + " > " + rule.endYear); } - parseOptional(st.nextToken()); // type is unused - parseMonthDayTime(st, rule); - rule.savingsAmount = parsePeriod(st.nextToken()); - rule.text = parseOptional(st.nextToken()); + parseOptional(s.next()); // type is unused + parseMonthDayTime(s, rule); + rule.savingsAmount = parsePeriod(s.next()); + rule.text = parseOptional(s.next()); } /** * Parses a Zone line. * - * @param st the tokenizer, not null + * @param s the line scanner, not null * @return true if the zone is complete */ - private boolean parseZoneLine(StringTokenizer st, List zoneList) { + private boolean parseZoneLine(Scanner s, List zoneList) { TZDBZone zone = new TZDBZone(); zoneList.add(zone); - zone.standardOffset = parseOffset(st.nextToken()); - String savingsRule = parseOptional(st.nextToken()); + zone.standardOffset = parseOffset(s.next()); + String savingsRule = parseOptional(s.next()); if (savingsRule == null) { zone.fixedSavingsSecs = 0; zone.savingsRule = null; @@ -580,11 +412,11 @@ zone.savingsRule = savingsRule; } } - zone.text = st.nextToken(); - if (st.hasMoreTokens()) { - zone.year = Integer.parseInt(st.nextToken()); - if (st.hasMoreTokens()) { - parseMonthDayTime(st, zone); + zone.text = s.next(); + if (s.hasNext()) { + zone.year = Integer.parseInt(s.next()); + if (s.hasNext()) { + parseMonthDayTime(s, zone); } return false; } else { @@ -595,13 +427,13 @@ /** * Parses a Rule line. * - * @param st the tokenizer, not null + * @param s the line scanner, not null * @param mdt the object to parse into, not null */ - private void parseMonthDayTime(StringTokenizer st, TZDBMonthDayTime mdt) { - mdt.month = parseMonth(st.nextToken()); - if (st.hasMoreTokens()) { - String dayRule = st.nextToken(); + private void parseMonthDayTime(Scanner s, TZDBMonthDayTime mdt) { + mdt.month = parseMonth(s); + if (s.hasNext()) { + String dayRule = s.next(); if (dayRule.startsWith("last")) { mdt.dayOfMonth = -1; mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(4)); @@ -621,8 +453,8 @@ } mdt.dayOfMonth = Integer.parseInt(dayRule); } - if (st.hasMoreTokens()) { - String timeStr = st.nextToken(); + if (s.hasNext()) { + String timeStr = s.next(); int secsOfDay = parseSecs(timeStr); if (secsOfDay == 86400) { mdt.endOfDay = true; @@ -635,30 +467,43 @@ } } - private int parseYear(String str, int defaultYear) { - if (YEAR.reset(str).matches()) { - if (YEAR.group("min") != null) { - //return YEAR_MIN_VALUE; + private int parseYear(Scanner s, int defaultYear) { + if (s.hasNext(YEAR)) { + s.next(YEAR); + MatchResult mr = s.match(); + if (mr.group(1) != null) { return 1900; // systemv has min - } else if (YEAR.group("max") != null) { + } else if (mr.group(2) != null) { return YEAR_MAX_VALUE; - } else if (YEAR.group("only") != null) { + } else if (mr.group(3) != null) { return defaultYear; } - return Integer.parseInt(YEAR.group("year")); + return Integer.parseInt(mr.group(4)); + /* + if (mr.group("min") != null) { + //return YEAR_MIN_VALUE; + return 1900; // systemv has min + } else if (mr.group("max") != null) { + return YEAR_MAX_VALUE; + } else if (mr.group("only") != null) { + return defaultYear; + } + return Integer.parseInt(mr.group("year")); + */ } - throw new IllegalArgumentException("Unknown year: " + str); + throw new IllegalArgumentException("Unknown year: " + s.next()); } - private int parseMonth(String str) { - if (MONTH.reset(str).matches()) { + private int parseMonth(Scanner s) { + if (s.hasNext(MONTH)) { + s.next(MONTH); for (int moy = 1; moy < 13; moy++) { - if (MONTH.group(moy) != null) { + if (s.match().group(moy) != null) { return moy; } } } - throw new IllegalArgumentException("Unknown month: " + str); + throw new IllegalArgumentException("Unknown month: " + s.next()); } private int parseDayOfWeek(String str) { @@ -729,7 +574,6 @@ } } - //----------------------------------------------------------------------- /** * Build the rules, zones and links into real zones. * @@ -744,8 +588,7 @@ for (TZDBZone tzdbZone : tzdbZones) { bld = tzdbZone.addToBuilder(bld, rules); } - ZoneRules buildRules = bld.toRules(zoneId); - builtZones.put(zoneId, buildRules); + builtZones.put(zoneId, bld.toRules(zoneId)); } // build aliases @@ -758,25 +601,25 @@ printVerbose("Relinking alias " + aliasId + " to " + realId); realRules = builtZones.get(realId); if (realRules == null) { - throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId + "' for '" + version + "'"); + throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId); } links.put(aliasId, realId); - } builtZones.put(aliasId, realRules); } - // remove UTC and GMT - //builtZones.remove("UTC"); - //builtZones.remove("GMT"); - //builtZones.remove("GMT0"); + // builtZones.remove("UTC"); + // builtZones.remove("GMT"); + // builtZones.remove("GMT0"); builtZones.remove("GMT+0"); builtZones.remove("GMT-0"); links.remove("GMT+0"); links.remove("GMT-0"); + // remove ROC, which is not supported in j.u.tz + builtZones.remove("ROC"); + links.remove("ROC"); } - //----------------------------------------------------------------------- /** * Prints a verbose message. * @@ -788,7 +631,6 @@ } } - //----------------------------------------------------------------------- /** * Class representing a month-day-time in the TZDB file. */ @@ -893,5 +735,4 @@ return ldt; } } - } diff -r 5f46a666e06d -r 84df34924f67 makefiles/CopyFiles.gmk --- a/makefiles/CopyFiles.gmk Sat Apr 13 20:16:00 2013 +0100 +++ b/makefiles/CopyFiles.gmk Sat Apr 13 21:51:36 2013 +0100 @@ -174,6 +174,13 @@ COPY_FILES += $(LIBDIR)/calendars.properties +$(LIBDIR)/hijrah-config-umalqura.properties: $(CALENDARS_SRC)/hijrah-config-umalqura.properties + $(MKDIR) -p $(@D) + $(RM) $@ + $(CP) $< $@ + +COPY_FILES += $(LIBDIR)/hijrah-config-umalqura.properties + ########################################################################################## ifeq ($(OPENJDK_TARGET_OS),windows) diff -r 5f46a666e06d -r 84df34924f67 makefiles/CreateJars.gmk --- a/makefiles/CreateJars.gmk Sat Apr 13 20:16:00 2013 +0100 +++ b/makefiles/CreateJars.gmk Sat Apr 13 21:51:36 2013 +0100 @@ -73,11 +73,6 @@ ########################################################################################## -$(IMAGES_OUTPUTDIR)/lib/tzdb.jar: $(JDK_OUTPUTDIR)/lib/tzdb.jar - $(install-file) - -########################################################################################## - LOCALEDATA_INCLUDE_LOCALES := ar be bg ca cs da de el es et fi fr ga hi hr hu in is it \ iw ja ko lt lv mk ms mt nl no pl pt ro ru sk sl sq sr sv \ th tr uk vi zh diff -r 5f46a666e06d -r 84df34924f67 makefiles/GendataTZDB.gmk --- a/makefiles/GendataTZDB.gmk Sat Apr 13 20:16:00 2013 +0100 +++ b/makefiles/GendataTZDB.gmk Sat Apr 13 21:51:36 2013 +0100 @@ -29,16 +29,13 @@ # Time zone data file creation # TZDATA_DIR := $(JDK_TOPDIR)/make/sun/javazic/tzdata -TZDATA_VER := $(subst tzdata,,$(shell $(GREP) '^tzdata' $(TZDATA_DIR)/VERSION)) TZDATA_TZFILE := africa antarctica asia australasia europe northamerica pacificnew southamerica backward etcetera gmt jdk11_backward TZDATA_TZFILES := $(addprefix $(TZDATA_DIR)/,$(TZDATA_TZFILE)) -GENDATA_TZDB_DST := $(JDK_OUTPUTDIR)/lib -GENDATA_TZDB_JAR := tzdb.jar +GENDATA_TZDB_DAT := $(JDK_OUTPUTDIR)/lib/tzdb.dat -$(GENDATA_TZDB_DST)/$(GENDATA_TZDB_JAR) : $(TZDATA_TZFILES) - $(RM) $(GENDATA_TZDB_DST)/$(GENDATA_TZDB_JAR) - echo building tzdb from version $(TZDATA_VER) - $(TOOL_TZDB) -version $(TZDATA_VER) -srcdir $(TZDATA_DIR) -dstdir $(GENDATA_TZDB_DST) $(TZDATA_TZFILE) +$(GENDATA_TZDB_DAT) : $(TZDATA_TZFILES) + $(RM) $(GENDATA_TZDB_DAT) + $(TOOL_TZDB) -srcdir $(TZDATA_DIR) -dstfile $(GENDATA_TZDB_DAT) $(TZDATA_TZFILE) -GENDATA_TZDB += $(GENDATA_TZDB_DST)/$(GENDATA_TZDB_JAR) +GENDATA_TZDB += $(GENDATA_TZDB_DAT) diff -r 5f46a666e06d -r 84df34924f67 makefiles/mapfiles/libjava/mapfile-vers --- a/makefiles/mapfiles/libjava/mapfile-vers Sat Apr 13 20:16:00 2013 +0100 +++ b/makefiles/mapfiles/libjava/mapfile-vers Sat Apr 13 21:51:36 2013 +0100 @@ -217,7 +217,7 @@ Java_java_lang_Throwable_fillInStackTrace; Java_java_lang_Throwable_getStackTraceDepth; Java_java_lang_Throwable_getStackTraceElement; - Java_java_lang_UNIXProcess_initIDs; + Java_java_lang_UNIXProcess_init; Java_java_lang_UNIXProcess_waitForProcessExit; Java_java_lang_UNIXProcess_forkAndExec; Java_java_lang_UNIXProcess_destroyProcess; diff -r 5f46a666e06d -r 84df34924f67 makefiles/profile-includes.txt --- a/makefiles/profile-includes.txt Sat Apr 13 20:16:00 2013 +0100 +++ b/makefiles/profile-includes.txt Sat Apr 13 21:51:36 2013 +0100 @@ -66,6 +66,7 @@ ext/sunec.jar \ ext/sunjce_provider.jar \ ext/sunpkcs11.jar \ + hijrah-config-umalqura.properties \ jce.jar \ jsse.jar \ logging.properties \ @@ -80,7 +81,7 @@ security/java.security \ security/local_policy.jar \ security/trusted.libraries \ - tzdb.jar + tzdb.dat PROFILE_1_JRE_OTHER_FILES := \ COPYRIGHT \ @@ -100,9 +101,7 @@ resources.jar \ rt.jar \ security/US_export_policy.jar \ - security/local_policy.jar \ - tzdb.jar - + security/local_policy.jar PROFILE_2_JRE_BIN_FILES := \ rmid$(EXE_SUFFIX) \ diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java --- a/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Sat Apr 13 21:51:36 2013 +0100 @@ -184,7 +184,7 @@ for (int i=0; i { /** + * The unit of granularity to interpret the value. Null if + * this {@code FileTime} is converted from an {@code Instant}, + * the {@code value} and {@code unit} pair will not be used + * in this scenario. + */ + private final TimeUnit unit; + + /** * The value since the epoch; can be negative. */ private final long value; /** - * The unit of granularity to interpret the value. + * The value as Instant (created lazily, if not from an instant) */ - private final TimeUnit unit; + private Instant instant; /** * The value return by toString (created lazily) @@ -66,27 +72,12 @@ private String valueAsString; /** - * The value in days and excess nanos (created lazily) - */ - private DaysAndNanos daysAndNanos; - - /** - * Returns a DaysAndNanos object representing the value. - */ - private DaysAndNanos asDaysAndNanos() { - if (daysAndNanos == null) - daysAndNanos = new DaysAndNanos(value, unit); - return daysAndNanos; - } - - /** * Initializes a new instance of this class. */ - private FileTime(long value, TimeUnit unit) { - if (unit == null) - throw new NullPointerException(); + private FileTime(long value, TimeUnit unit, Instant instant) { this.value = value; this.unit = unit; + this.instant = instant; } /** @@ -102,7 +93,8 @@ * @return a {@code FileTime} representing the given value */ public static FileTime from(long value, TimeUnit unit) { - return new FileTime(value, unit); + Objects.requireNonNull(unit, "unit"); + return new FileTime(value, unit, null); } /** @@ -115,7 +107,22 @@ * @return a {@code FileTime} representing the given value */ public static FileTime fromMillis(long value) { - return new FileTime(value, TimeUnit.MILLISECONDS); + return new FileTime(value, TimeUnit.MILLISECONDS, null); + } + + /** + * Returns a {@code FileTime} representing the same point of time value + * on the time-line as the provided {@code Instant} object. + * + * @param instant + * the instant to convert + * @return a {@code FileTime} representing the same point on the time-line + * as the provided instant + * @since 1.8 + */ + public static FileTime from(Instant instant) { + Objects.requireNonNull(instant, "instant"); + return new FileTime(0, null, instant); } /** @@ -132,7 +139,22 @@ * since the epoch (1970-01-01T00:00:00Z); can be negative */ public long to(TimeUnit unit) { - return unit.convert(this.value, this.unit); + Objects.requireNonNull(unit, "unit"); + if (this.unit != null) { + return unit.convert(this.value, this.unit); + } else { + long secs = unit.convert(instant.getEpochSecond(), TimeUnit.SECONDS); + if (secs == Long.MIN_VALUE || secs == Long.MAX_VALUE) { + return secs; + } + long nanos = unit.convert(instant.getNano(), TimeUnit.NANOSECONDS); + long r = secs + nanos; + // Math.addExact() variant + if (((secs ^ r) & (nanos ^ r)) < 0) { + return (secs < 0) ? Long.MIN_VALUE : Long.MAX_VALUE; + } + return r; + } } /** @@ -145,7 +167,110 @@ * @return the value in milliseconds, since the epoch (1970-01-01T00:00:00Z) */ public long toMillis() { - return unit.toMillis(value); + if (unit != null) { + return unit.toMillis(value); + } else { + long secs = instant.getEpochSecond(); + int nanos = instant.getNano(); + // Math.multiplyExact() variant + long r = secs * 1000; + long ax = Math.abs(secs); + if (((ax | 1000) >>> 31 != 0)) { + if ((r / 1000) != secs) { + return (secs < 0) ? Long.MIN_VALUE : Long.MAX_VALUE; + } + } + return r + nanos / 1000_000; + } + } + + /** + * Time unit constants for conversion. + */ + private static final long HOURS_PER_DAY = 24L; + private static final long MINUTES_PER_HOUR = 60L; + private static final long SECONDS_PER_MINUTE = 60L; + private static final long SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; + private static final long SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY; + private static final long MILLIS_PER_SECOND = 1000L; + private static final long MICROS_PER_SECOND = 1000_000L; + private static final long NANOS_PER_SECOND = 1000_000_000L; + private static final int NANOS_PER_MILLI = 1000_000; + private static final int NANOS_PER_MICRO = 1000; + // The epoch second of Instant.MIN. + private static final long MIN_SECOND = -31557014167219200L; + // The epoch second of Instant.MAX. + private static final long MAX_SECOND = 31556889864403199L; + + /* + * Scale d by m, checking for overflow. + */ + private static long scale(long d, long m, long over) { + if (d > over) return Long.MAX_VALUE; + if (d < -over) return Long.MIN_VALUE; + return d * m; + } + + /** + * Converts this {@code FileTime} object to an {@code Instant}. + * + *

The conversion creates an {@code Instant} that represents the + * same point on the time-line as this {@code FileTime}. + * + *

{@code FileTime} can store points on the time-line further in the + * future and further in the past than {@code Instant}. Conversion + * from such further time points saturates to {@link Instant.MIN} if + * earlier than {@code Instant.MIN} or {@link Instant.MAX} if later + * than {@code Instant.MAX}. + * + * @return an instant representing the same point on the time-line as + * this {@code FileTime} object + * @since 1.8 + */ + public Instant toInstant() { + if (instant == null) { + long secs = 0L; + int nanos = 0; + switch (unit) { + case DAYS: + secs = scale(value, SECONDS_PER_DAY, + Long.MAX_VALUE/SECONDS_PER_DAY); + break; + case HOURS: + secs = scale(value, SECONDS_PER_HOUR, + Long.MAX_VALUE/SECONDS_PER_HOUR); + break; + case MINUTES: + secs = scale(value, SECONDS_PER_MINUTE, + Long.MAX_VALUE/SECONDS_PER_MINUTE); + break; + case SECONDS: + secs = value; + break; + case MILLISECONDS: + secs = Math.floorDiv(value, MILLIS_PER_SECOND); + nanos = (int)Math.floorMod(value, MILLIS_PER_SECOND) + * NANOS_PER_MILLI; + break; + case MICROSECONDS: + secs = Math.floorDiv(value, MICROS_PER_SECOND); + nanos = (int)Math.floorMod(value, MICROS_PER_SECOND) + * NANOS_PER_MICRO; + break; + case NANOSECONDS: + secs = Math.floorDiv(value, NANOS_PER_SECOND); + nanos = (int)Math.floorMod(value, NANOS_PER_SECOND); + break; + default : throw new AssertionError("Unit not handled"); + } + if (secs <= MIN_SECOND) + instant = Instant.MIN; + else if (secs >= MAX_SECOND) + instant = Instant.MAX; + else + instant = Instant.ofEpochSecond(secs, nanos); + } + return instant; } /** @@ -176,8 +301,25 @@ */ @Override public int hashCode() { - // hashcode of days/nanos representation to satisfy contract with equals - return asDaysAndNanos().hashCode(); + // hashcode of instant representation to satisfy contract with equals + return toInstant().hashCode(); + } + + private long toDays() { + if (unit != null) { + return unit.toDays(value); + } else { + return TimeUnit.SECONDS.toDays(toInstant().getEpochSecond()); + } + } + + private long toExcessNanos(long days) { + if (unit != null) { + return unit.toNanos(value - unit.convert(days, TimeUnit.DAYS)); + } else { + return TimeUnit.SECONDS.toNanos(toInstant().getEpochSecond() + - TimeUnit.DAYS.toSeconds(days)); + } } /** @@ -194,14 +336,52 @@ @Override public int compareTo(FileTime other) { // same granularity - if (unit == other.unit) { - return (value < other.value) ? -1 : (value == other.value ? 0 : 1); + if (unit != null && unit == other.unit) { + return Long.compare(value, other.value); } else { - // compare using days/nanos representation when unit differs - return asDaysAndNanos().compareTo(other.asDaysAndNanos()); + // compare using instant representation when unit differs + long secs = toInstant().getEpochSecond(); + long secsOther = other.toInstant().getEpochSecond(); + int cmp = Long.compare(secs, secsOther); + if (cmp != 0) { + return cmp; + } + cmp = Long.compare(toInstant().getNano(), other.toInstant().getNano()); + if (cmp != 0) { + return cmp; + } + if (secs != MAX_SECOND && secs != MIN_SECOND) { + return 0; + } + // if both this and other's Instant reps are MIN/MAX, + // use daysSinceEpoch and nanosOfDays, which will not + // saturate during calculation. + long days = toDays(); + long daysOther = other.toDays(); + if (days == daysOther) { + return Long.compare(toExcessNanos(days), other.toExcessNanos(daysOther)); + } + return Long.compare(days, daysOther); } } + // days in a 400 year cycle = 146097 + // days in a 10,000 year cycle = 146097 * 25 + // seconds per day = 86400 + private static final long DAYS_PER_10000_YEARS = 146097L * 25L; + private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L; + private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L; + + // append year/month/day/hour/minute/second/nano with width and 0 padding + private StringBuilder append(StringBuilder sb, int w, int d) { + while (w > 0) { + sb.append((char)(d/w + '0')); + d = d % w; + w /= 10; + } + return sb; + } + /** * Returns the string representation of this {@code FileTime}. The string * is returned in the 0) { - sb.append('0'); - } - if (s.charAt(len-1) == '0') { - // drop trailing zeros - len--; - while (s.charAt(len-1) == '0') - len--; - sb.append(s.substring(0, len)); - } else { - sb.append(s); - } - fractionAsString = sb.toString(); + if (valueAsString == null) { + long secs = 0L; + int nanos = 0; + if (instant == null && unit.compareTo(TimeUnit.SECONDS) >= 0) { + secs = unit.toSeconds(value); + } else { + secs = toInstant().getEpochSecond(); + nanos = toInstant().getNano(); + } + LocalDateTime ldt; + int year = 0; + if (secs >= -SECONDS_0000_TO_1970) { + // current era + long zeroSecs = secs - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970; + long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1; + long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS); + ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, nanos, ZoneOffset.UTC); + year = ldt.getYear() + (int)hi * 10000; + } else { + // before current era + long zeroSecs = secs + SECONDS_0000_TO_1970; + long hi = zeroSecs / SECONDS_PER_10000_YEARS; + long lo = zeroSecs % SECONDS_PER_10000_YEARS; + ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, nanos, ZoneOffset.UTC); + year = ldt.getYear() + (int)hi * 10000; + } + if (year <= 0) { + year = year - 1; + } + int fraction = ldt.getNano(); + StringBuilder sb = new StringBuilder(64); + sb.append(year < 0 ? "-" : ""); + year = Math.abs(year); + if (year < 10000) { + append(sb, 1000, Math.abs(year)); + } else { + sb.append(String.valueOf(year)); + } + sb.append('-'); + append(sb, 10, ldt.getMonthValue()); + sb.append('-'); + append(sb, 10, ldt.getDayOfMonth()); + sb.append('T'); + append(sb, 10, ldt.getHour()); + sb.append(':'); + append(sb, 10, ldt.getMinute()); + sb.append(':'); + append(sb, 10, ldt.getSecond()); + if (fraction != 0) { + sb.append('.'); + // adding leading zeros and stripping any trailing zeros + int w = 100_000_000; + while (fraction % 10 == 0) { + fraction /= 10; + w /= 10; } + append(sb, w, fraction); } - - // create calendar to use with formatter. - GregorianCalendar cal = - new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.ROOT); - if (value < 0L) - cal.setGregorianChange(new Date(Long.MIN_VALUE)); - cal.setTimeInMillis(ms); - - // years are negative before common era - String sign = (cal.get(Calendar.ERA) == GregorianCalendar.BC) ? "-" : ""; - - // [-]YYYY-MM-DDThh:mm:ss[.s]Z - v = new Formatter(Locale.ROOT) - .format("%s%tFT%tR:%tS%sZ", sign, cal, cal, cal, fractionAsString) - .toString(); - valueAsString = v; + sb.append('Z'); + valueAsString = sb.toString(); } - return v; - } - - /** - * Represents a FileTime's value as two longs: the number of days since - * the epoch, and the excess (in nanoseconds). This is used for comparing - * values with different units of granularity. - */ - private static class DaysAndNanos implements Comparable { - // constants for conversion - private static final long C0 = 1L; - private static final long C1 = C0 * 24L; - private static final long C2 = C1 * 60L; - private static final long C3 = C2 * 60L; - private static final long C4 = C3 * 1000L; - private static final long C5 = C4 * 1000L; - private static final long C6 = C5 * 1000L; - - /** - * The value (in days) since the epoch; can be negative. - */ - private final long days; - - /** - * The excess (in nanoseconds); can be negative if days <= 0. - */ - private final long excessNanos; - - /** - * Initializes a new instance of this class. - */ - DaysAndNanos(long value, TimeUnit unit) { - long scale; - switch (unit) { - case DAYS : scale = C0; break; - case HOURS : scale = C1; break; - case MINUTES : scale = C2; break; - case SECONDS : scale = C3; break; - case MILLISECONDS : scale = C4; break; - case MICROSECONDS : scale = C5; break; - case NANOSECONDS : scale = C6; break; - default : throw new AssertionError("Unit not handled"); - } - this.days = unit.toDays(value); - this.excessNanos = unit.toNanos(value - (this.days * scale)); - } - - /** - * Returns the fraction of a second, in nanoseconds. - */ - long fractionOfSecondInNanos() { - return excessNanos % (1000L * 1000L * 1000L); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof DaysAndNanos) ? - compareTo((DaysAndNanos)obj) == 0 : false; - } - - @Override - public int hashCode() { - return (int)(days ^ (days >>> 32) ^ - excessNanos ^ (excessNanos >>> 32)); - } - - @Override - public int compareTo(DaysAndNanos other) { - if (this.days != other.days) - return (this.days < other.days) ? -1 : 1; - return (this.excessNanos < other.excessNanos) ? -1 : - (this.excessNanos == other.excessNanos) ? 0 : 1; - } + return valueAsString; } } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/DayOfWeek.java --- a/src/share/classes/java/time/DayOfWeek.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/DayOfWeek.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,13 +61,13 @@ */ package java.time; +import java.time.temporal.UnsupportedTemporalTypeException; import static java.time.temporal.ChronoField.DAY_OF_WEEK; import static java.time.temporal.ChronoUnit.DAYS; import java.time.format.DateTimeFormatterBuilder; import java.time.format.TextStyle; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -259,7 +259,7 @@ *

* If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the * range of the day-of-week, from 1 to 7, will be returned. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

* If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -269,6 +269,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -288,7 +289,7 @@ *

* If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the * value of the day-of-week, from 1 to 7, will be returned. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

* If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -297,7 +298,10 @@ * * @param field the field to get, not null * @return the value for the field, within the valid range of values - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -317,7 +321,7 @@ *

* If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the * value of the day-of-week, from 1 to 7, will be returned. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

* If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -327,6 +331,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -334,7 +339,7 @@ if (field == DAY_OF_WEEK) { return getValue(); } else if (field instanceof ChronoField) { - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } @@ -393,7 +398,7 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.precision()) { + if (query == TemporalQuery.precision()) { return (R) DAYS; } return TemporalAccessor.super.query(query); @@ -409,8 +414,8 @@ * passing {@link ChronoField#DAY_OF_WEEK} as the field. * Note that this adjusts forwards or backwards within a Monday to Sunday week. * See {@link WeekFields#dayOfWeek} for localized week start days. - * See {@link java.time.temporal.Adjusters Adjusters} for other adjusters - * with more control, such as {@code next(MONDAY)}. + * See {@code TemporalAdjuster} for other adjusters with more control, + * such as {@code next(MONDAY)}. *

* In most cases, it is clearer to reverse the calling pattern by using * {@link Temporal#with(TemporalAdjuster)}: diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/Duration.java --- a/src/share/classes/java/time/Duration.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/Duration.java Sat Apr 13 21:51:36 2013 +0100 @@ -85,6 +85,7 @@ import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -302,31 +303,32 @@ //----------------------------------------------------------------------- /** - * Obtains a {@code Duration} representing the duration between two instants. + * Obtains an instance of {@code Duration} from a temporal amount. + *

+ * This obtains a duration based on the specified amount. + * A {@code TemporalAmount} represents an amount of time, which may be + * date-based or time-based, which this factory extracts to a duration. *

- * This calculates the duration between two temporal objects of the same type. - * The difference in seconds is calculated using - * {@link Temporal#periodUntil(Temporal, TemporalUnit)}. - * The difference in nanoseconds is calculated using by querying the - * {@link ChronoField#NANO_OF_SECOND NANO_OF_SECOND} field. - *

- * The result of this method can be a negative period if the end is before the start. - * To guarantee to obtain a positive duration call {@link #abs()} on the result. + * The conversion loops around the set of units from the amount and uses + * the {@linkplain TemporalUnit#getDuration() duration} of the unit to + * calculate the total {@code Duration}. + * Only a subset of units are accepted by this method. The unit must either + * have an {@linkplain TemporalUnit#isDurationEstimated() exact duration} + * or be {@link ChronoUnit#DAYS} which is treated as 24 hours. + * If any other units are found then an exception is thrown. * - * @param startInclusive the start instant, inclusive, not null - * @param endExclusive the end instant, exclusive, not null - * @return a {@code Duration}, not null - * @throws ArithmeticException if the calculation exceeds the capacity of {@code Duration} + * @param amount the temporal amount to convert, not null + * @return the equivalent duration, not null + * @throws DateTimeException if unable to convert to a {@code Duration} + * @throws ArithmeticException if numeric overflow occurs */ - public static Duration between(Temporal startInclusive, Temporal endExclusive) { - long secs = startInclusive.periodUntil(endExclusive, SECONDS); - long nanos; - try { - nanos = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND); - } catch (DateTimeException ex) { - nanos = 0; + public static Duration from(TemporalAmount amount) { + Objects.requireNonNull(amount, "amount"); + Duration duration = ZERO; + for (TemporalUnit unit : amount.getUnits()) { + duration = duration.plus(amount.get(unit), unit); } - return ofSeconds(secs, nanos); + return duration; } //----------------------------------------------------------------------- @@ -360,14 +362,14 @@ *

* Examples: *

-     *    "PT20.345S" -> parses as "20.345 seconds"
-     *    "PT15M"     -> parses as "15 minutes" (where a minute is 60 seconds)
-     *    "PT10H"     -> parses as "10 hours" (where an hour is 3600 seconds)
-     *    "P2D"       -> parses as "2 days" (where a day is 24 hours or 86400 seconds)
-     *    "P2DT3H4M"  -> parses as "2 days, 3 hours and 4 minutes"
-     *    "P-6H3M"    -> parses as "-6 hours and +3 minutes"
-     *    "-P6H3M"    -> parses as "-6 hours and -3 minutes"
-     *    "-P-6H+3M"  -> parses as "+6 hours and -3 minutes"
+     *    "PT20.345S" -- parses as "20.345 seconds"
+     *    "PT15M"     -- parses as "15 minutes" (where a minute is 60 seconds)
+     *    "PT10H"     -- parses as "10 hours" (where an hour is 3600 seconds)
+     *    "P2D"       -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
+     *    "P2DT3H4M"  -- parses as "2 days, 3 hours and 4 minutes"
+     *    "P-6H3M"    -- parses as "-6 hours and +3 minutes"
+     *    "-P6H3M"    -- parses as "-6 hours and -3 minutes"
+     *    "-P-6H+3M"  -- parses as "+6 hours and -3 minutes"
      * 
* * @param text the text to parse, not null @@ -439,6 +441,44 @@ //----------------------------------------------------------------------- /** + * Obtains a {@code Duration} representing the duration between two instants. + *

+ * This calculates the duration between two temporal objects of the same type. + * The specified temporal objects must support the {@link ChronoUnit#SECONDS SECONDS} unit. + * For full accuracy, either the {@link ChronoUnit#NANOS NANOS} unit or the + * {@link ChronoField#NANO_OF_SECOND NANO_OF_SECOND} field should be supported. + *

+ * The result of this method can be a negative period if the end is before the start. + * To guarantee to obtain a positive duration call {@link #abs()} on the result. + * + * @param startInclusive the start instant, inclusive, not null + * @param endExclusive the end instant, exclusive, not null + * @return a {@code Duration}, not null + * @throws DateTimeException if the seconds between the temporals cannot be obtained + * @throws ArithmeticException if the calculation exceeds the capacity of {@code Duration} + */ + public static Duration between(Temporal startInclusive, Temporal endExclusive) { + try { + return ofNanos(startInclusive.periodUntil(endExclusive, NANOS)); + } catch (DateTimeException | ArithmeticException ex) { + long secs = startInclusive.periodUntil(endExclusive, SECONDS); + long nanos; + try { + nanos = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND); + if (secs > 0 && nanos < 0) { + secs++; + } else if (secs < 0 && nanos > 0) { + secs--; + } + } catch (DateTimeException ex2) { + nanos = 0; + } + return ofSeconds(secs, nanos); + } + } + + //----------------------------------------------------------------------- + /** * Obtains an instance of {@code Duration} using seconds and nanoseconds. * * @param seconds the length of the duration in seconds, positive or negative @@ -474,6 +514,7 @@ * @param unit the {@code TemporalUnit} for which to return the value * @return the long value of the unit * @throws DateTimeException if the unit is not supported + * @throws UnsupportedTemporalTypeException if the unit is not supported */ @Override public long get(TemporalUnit unit) { @@ -482,7 +523,7 @@ } else if (unit == NANOS) { return nanos; } else { - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } } @@ -637,6 +678,7 @@ * @param amountToAdd the amount of the period, measured in terms of the unit, positive or negative * @param unit the unit that the period is measured in, must have an exact duration, not null * @return a {@code Duration} based on this duration with the specified duration added, not null + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ public Duration plus(long amountToAdd, TemporalUnit unit) { @@ -645,7 +687,7 @@ return plus(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY), 0); } if (unit.isDurationEstimated()) { - throw new DateTimeException("Unit must not have an estimated duration"); + throw new UnsupportedTemporalTypeException("Unit must not have an estimated duration"); } if (amountToAdd == 0) { return this; @@ -1130,9 +1172,9 @@ * @throws ArithmeticException if numeric overflow occurs */ public long toNanos() { - long millis = Math.multiplyExact(seconds, NANOS_PER_SECOND); - millis = Math.addExact(millis, nanos); - return millis; + long totalNanos = Math.multiplyExact(seconds, NANOS_PER_SECOND); + totalNanos = Math.addExact(totalNanos, nanos); + return totalNanos; } //----------------------------------------------------------------------- @@ -1199,10 +1241,10 @@ *

* Examples: *

-     *    "20.345 seconds"                 -> "PT20.345S
-     *    "15 minutes" (15 * 60 seconds)   -> "PT15M"
-     *    "10 hours" (10 * 3600 seconds)   -> "PT10H"
-     *    "2 days" (2 * 86400 seconds)     -> "PT48H"
+     *    "20.345 seconds"                 -- "PT20.345S
+     *    "15 minutes" (15 * 60 seconds)   -- "PT15M"
+     *    "10 hours" (10 * 3600 seconds)   -- "PT10H"
+     *    "2 days" (2 * 86400 seconds)     -- "PT48H"
      * 
* Note that multiples of 24 hours are not output as days to avoid confusion * with {@code Period}. diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/Instant.java --- a/src/share/classes/java/time/Instant.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/Instant.java Sat Apr 13 21:51:36 2013 +0100 @@ -81,7 +81,6 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -89,6 +88,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; @@ -148,6 +148,8 @@ *
  • other times during the day will be broadly in line with the agreed international civil time
  • *
  • the day will be divided into exactly 86400 subdivisions, referred to as "seconds"
  • *
  • the Java "second" may differ from an SI second
  • + *
  • a well-defined algorithm must be specified to map each second in the accurate agreed + * international civil time to each "second" in this time-scale
  • *

    * Agreed international civil time is the base time-scale agreed by international convention, * which in 2012 is UTC (with leap-seconds). @@ -155,6 +157,14 @@ * In 2012, the definition of the Java time-scale is the same as UTC for all days except * those where a leap-second occurs. On days where a leap-second does occur, the time-scale * effectively eliminates the leap-second, maintaining the fiction of 86400 seconds in the day. + * The approved well-defined algorithm to eliminate leap-seconds is specified as + * UTC-SLS. + *

    + * UTC-SLS is a simple algorithm that smoothes the leap-second over the last 1000 seconds of + * the day, making each of the last 1000 seconds 1/1000th longer or shorter than an SI second. + * Implementations built on an accurate leap-second aware time source should use UTC-SLS. + * Use of a different algorithm risks confusion and misinterpretation of instants around a + * leap-second and is discouraged. *

    * The main benefit of always dividing the day into 86400 subdivisions is that it matches the * expectations of most users of the API. The alternative is to force every user to understand @@ -163,16 +173,10 @@ * Most applications also do not have a problem with a second being a very small amount longer or * shorter than a real SI second during a leap-second. *

    - * If an application does have access to an accurate clock that reports leap-seconds, then the - * recommended technique to implement the Java time-scale is to use the UTC-SLS convention. - * UTC-SLS effectively smoothes the - * leap-second over the last 1000 seconds of the day, making each of the last 1000 "seconds" - * 1/1000th longer or shorter than a real SI second. - *

    * One final problem is the definition of the agreed international civil time before the * introduction of modern UTC in 1972. This includes the Java epoch of {@code 1970-01-01}. * It is intended that instants before 1972 be interpreted based on the solar day divided - * into 86400 subdivisions. + * into 86400 subdivisions, as per the principles of UT1. *

    * The Java time-scale is used for all date-time classes. * This includes {@code Instant}, {@code LocalDate}, {@code LocalTime}, {@code OffsetDateTime}, @@ -210,7 +214,7 @@ */ public static final Instant MIN = Instant.ofEpochSecond(MIN_SECOND, 0); /** - * The minimum supported {@code Instant}, '-1000000000-01-01T00:00Z'. + * The maximum supported {@code Instant}, '1000000000-12-31T23:59:59.999999999Z'. * This could be used by an application as a "far future" instant. *

    * This is one year later than the maximum {@code LocalDateTime}. @@ -292,9 +296,9 @@ * to ensure that the stored nanosecond is in the range 0 to 999,999,999. * For example, the following will result in the exactly the same instant: *

    -     *  Instant.ofSeconds(3, 1);
    -     *  Instant.ofSeconds(4, -999_999_999);
    -     *  Instant.ofSeconds(2, 1000_000_001);
    +     *  Instant.ofEpochSecond(3, 1);
    +     *  Instant.ofEpochSecond(4, -999_999_999);
    +     *  Instant.ofEpochSecond(2, 1000_000_001);
          * 
    * * @param epochSecond the number of seconds from 1970-01-01T00:00:00Z @@ -441,7 +445,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -451,6 +455,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override // override for Javadoc public ValueRange range(TemporalField field) { @@ -469,7 +474,7 @@ * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time, except {@code INSTANT_SECONDS} which is too * large to fit in an {@code int} and throws a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -478,7 +483,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc and performance @@ -490,7 +498,7 @@ case MILLI_OF_SECOND: return nanos / 1000_000; case INSTANT_SECONDS: INSTANT_SECONDS.checkValidIntValue(seconds); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return range(field).checkValidIntValue(field.getFrom(this), field); } @@ -505,7 +513,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -515,6 +523,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -526,7 +535,7 @@ case MILLI_OF_SECOND: return nanos / 1000_000; case INSTANT_SECONDS: return seconds; } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } @@ -610,7 +619,7 @@ * In all cases, if the new value is outside the valid range of values for the field * then a {@code DateTimeException} will be thrown. *

    - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -623,6 +632,7 @@ * @param newValue the new value of the field in the result * @return an {@code Instant} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -642,7 +652,7 @@ case NANO_OF_SECOND: return (newValue != nanos ? create(seconds, (int) newValue) : this); case INSTANT_SECONDS: return (newValue != seconds ? create(newValue, nanos) : this); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.adjustInto(this, newValue); } @@ -668,6 +678,7 @@ * @param unit the unit to truncate to, not null * @return an {@code Instant} based on this instant with the time truncated, not null * @throws DateTimeException if the unit is invalid for truncation + * @throws UnsupportedTemporalTypeException if the unit is not supported */ public Instant truncatedTo(TemporalUnit unit) { if (unit == ChronoUnit.NANOS) { @@ -675,11 +686,11 @@ } Duration unitDur = unit.getDuration(); if (unitDur.getSeconds() > LocalTime.SECONDS_PER_DAY) { - throw new DateTimeException("Unit is too large to be used for truncation"); + throw new UnsupportedTemporalTypeException("Unit is too large to be used for truncation"); } long dur = unitDur.toNanos(); if ((LocalTime.NANOS_PER_DAY % dur) != 0) { - throw new DateTimeException("Unit must divide into a standard day without remainder"); + throw new UnsupportedTemporalTypeException("Unit must divide into a standard day without remainder"); } long nod = (seconds % LocalTime.SECONDS_PER_DAY) * LocalTime.NANOS_PER_SECOND + nanos; long result = (nod / dur) * dur; @@ -754,7 +765,7 @@ * multiplied by 86,400 (24 hours). * *

    - * All other {@code ChronoUnit} instances will throw a {@code DateTimeException}. + * All other {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoUnit}, then the result of this method * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} @@ -767,6 +778,7 @@ * @param unit the unit of the amount to add, not null * @return an {@code Instant} based on this instant with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -782,7 +794,7 @@ case HALF_DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY / 2)); case DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY)); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.addTo(this, amountToAdd); } @@ -894,6 +906,7 @@ * @param unit the unit of the amount to subtract, not null * @return an {@code Instant} based on this instant with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -975,11 +988,12 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.precision()) { + if (query == TemporalQuery.precision()) { return (R) NANOS; } // inline TemporalAccessor.super.query(query) as an optimization - if (query == Queries.chronology() || query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + if (query == TemporalQuery.chronology() || query == TemporalQuery.zoneId() || + query == TemporalQuery.zone() || query == TemporalQuery.offset()) { return null; } return query.queryFrom(this); @@ -1028,14 +1042,15 @@ * For example, the period in days between two dates can be calculated * using {@code startInstant.periodUntil(endInstant, SECONDS)}. *

    - * This method operates in association with {@link TemporalUnit#between}. - * The result of this method is a {@code long} representing the amount of - * the specified unit. By contrast, the result of {@code between} is an - * object that can be used directly in addition/subtraction: + * There are two equivalent ways of using this method. + * The first is to invoke this method. + * The second is to use {@link TemporalUnit#between(Temporal, Temporal)}: *

    -     *   long period = start.periodUntil(end, SECONDS);   // this method
    -     *   dateTime.plus(SECONDS.between(start, end));      // use in plus/minus
    +     *   // these two lines are equivalent
    +     *   amount = start.periodUntil(end, SECONDS);
    +     *   amount = SECONDS.between(start, end);
          * 
    + * The choice should be made based on which makes the code more readable. *

    * The calculation is implemented in this method for {@link ChronoUnit}. * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, @@ -1053,6 +1068,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this date and the end date * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1074,18 +1090,26 @@ case HALF_DAYS: return secondsUntil(end) / (12 * SECONDS_PER_HOUR); case DAYS: return secondsUntil(end) / (SECONDS_PER_DAY); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.between(this, endInstant); } private long nanosUntil(Instant end) { - long secs = Math.multiplyExact(secondsUntil(end), NANOS_PER_SECOND); - return Math.addExact(secs, end.nanos - nanos); + long secsDiff = Math.subtractExact(end.seconds, seconds); + long totalNanos = Math.multiplyExact(secsDiff, NANOS_PER_SECOND); + return Math.addExact(totalNanos, end.nanos - nanos); } private long secondsUntil(Instant end) { - return Math.subtractExact(end.seconds, seconds); + long secsDiff = Math.subtractExact(end.seconds, seconds); + long nanosDiff = end.nanos - nanos; + if (secsDiff > 0 && nanosDiff < 0) { + secsDiff--; + } else if (secsDiff < 0 && nanosDiff > 0) { + secsDiff++; + } + return secsDiff; } //----------------------------------------------------------------------- diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/LocalDate.java --- a/src/share/classes/java/time/LocalDate.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/LocalDate.java Sat Apr 13 21:51:36 2013 +0100 @@ -69,9 +69,9 @@ import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.DAY_OF_YEAR; import static java.time.temporal.ChronoField.EPOCH_DAY; -import static java.time.temporal.ChronoField.EPOCH_MONTH; import static java.time.temporal.ChronoField.ERA; import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; import static java.time.temporal.ChronoField.YEAR; import java.io.DataInput; @@ -87,7 +87,6 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -95,6 +94,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.time.zone.ZoneOffsetTransition; import java.time.zone.ZoneRules; @@ -238,7 +238,7 @@ YEAR.checkValidValue(year); Objects.requireNonNull(month, "month"); DAY_OF_MONTH.checkValidValue(dayOfMonth); - return create(year, month, dayOfMonth); + return create(year, month.getValue(), dayOfMonth); } /** @@ -258,7 +258,7 @@ YEAR.checkValidValue(year); MONTH_OF_YEAR.checkValidValue(month); DAY_OF_MONTH.checkValidValue(dayOfMonth); - return create(year, Month.of(month), dayOfMonth); + return create(year, month, dayOfMonth); } //----------------------------------------------------------------------- @@ -287,7 +287,7 @@ moy = moy.plus(1); } int dom = dayOfYear - moy.firstDayOfYear(leap) + 1; - return create(year, moy, dom); + return new LocalDate(year, moy.getValue(), dom); } //----------------------------------------------------------------------- @@ -342,7 +342,7 @@ * A {@code TemporalAccessor} represents an arbitrary set of date and time information, * which this factory converts to an instance of {@code LocalDate}. *

    - * The conversion uses the {@link Queries#localDate()} query, which relies + * The conversion uses the {@link TemporalQuery#localDate()} query, which relies * on extracting the {@link ChronoField#EPOCH_DAY EPOCH_DAY} field. *

    * This method matches the signature of the functional interface {@link TemporalQuery} @@ -353,7 +353,7 @@ * @throws DateTimeException if unable to convert to a {@code LocalDate} */ public static LocalDate from(TemporalAccessor temporal) { - LocalDate date = temporal.query(Queries.localDate()); + LocalDate date = temporal.query(TemporalQuery.localDate()); if (date == null) { throw new DateTimeException("Unable to obtain LocalDate from TemporalAccessor: " + temporal.getClass()); } @@ -395,20 +395,34 @@ * Creates a local date from the year, month and day fields. * * @param year the year to represent, validated from MIN_YEAR to MAX_YEAR - * @param month the month-of-year to represent, validated not null + * @param month the month-of-year to represent, from 1 to 12, validated * @param dayOfMonth the day-of-month to represent, validated from 1 to 31 * @return the local date, not null * @throws DateTimeException if the day-of-month is invalid for the month-year */ - private static LocalDate create(int year, Month month, int dayOfMonth) { - if (dayOfMonth > 28 && dayOfMonth > month.length(IsoChronology.INSTANCE.isLeapYear(year))) { - if (dayOfMonth == 29) { - throw new DateTimeException("Invalid date 'February 29' as '" + year + "' is not a leap year"); - } else { - throw new DateTimeException("Invalid date '" + month.name() + " " + dayOfMonth + "'"); + private static LocalDate create(int year, int month, int dayOfMonth) { + if (dayOfMonth > 28) { + int dom = 31; + switch (month) { + case 2: + dom = (IsoChronology.INSTANCE.isLeapYear(year) ? 29 : 28); + break; + case 4: + case 6: + case 9: + case 11: + dom = 30; + break; + } + if (dayOfMonth > dom) { + if (dayOfMonth == 29) { + throw new DateTimeException("Invalid date 'February 29' as '" + year + "' is not a leap year"); + } else { + throw new DateTimeException("Invalid date '" + Month.of(month).name() + " " + dayOfMonth + "'"); + } } } - return new LocalDate(year, month.getValue(), dayOfMonth); + return new LocalDate(year, month, dayOfMonth); } /** @@ -431,7 +445,7 @@ day = Math.min(day, 30); break; } - return LocalDate.of(year, month, day); + return new LocalDate(year, month, day); } /** @@ -467,7 +481,7 @@ *

  • {@code ALIGNED_WEEK_OF_MONTH} *
  • {@code ALIGNED_WEEK_OF_YEAR} *
  • {@code MONTH_OF_YEAR} - *
  • {@code EPOCH_MONTH} + *
  • {@code PROLEPTIC_MONTH} *
  • {@code YEAR_OF_ERA} *
  • {@code YEAR} *
  • {@code ERA} @@ -498,7 +512,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -508,12 +522,13 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - if (f.isDateField()) { + if (f.isDateBased()) { switch (f) { case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); @@ -523,7 +538,7 @@ } return field.range(); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.rangeRefinedBy(this); } @@ -538,9 +553,9 @@ *

    * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid - * values based on this date, except {@code EPOCH_DAY} and {@code EPOCH_MONTH} + * values based on this date, except {@code EPOCH_DAY} and {@code PROLEPTIC_MONTH} * which are too large to fit in an {@code int} and throw a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -549,7 +564,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc and performance @@ -570,7 +588,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -580,6 +598,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -588,8 +607,8 @@ if (field == EPOCH_DAY) { return toEpochDay(); } - if (field == EPOCH_MONTH) { - return getEpochMonth(); + if (field == PROLEPTIC_MONTH) { + return getProlepticMonth(); } return get0(field); } @@ -603,20 +622,20 @@ case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1; case DAY_OF_MONTH: return day; case DAY_OF_YEAR: return getDayOfYear(); - case EPOCH_DAY: throw new DateTimeException("Field too large for an int: " + field); + case EPOCH_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'EpochDay' for get() method, use getLong() instead"); case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1; case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1; case MONTH_OF_YEAR: return month; - case EPOCH_MONTH: throw new DateTimeException("Field too large for an int: " + field); + case PROLEPTIC_MONTH: throw new UnsupportedTemporalTypeException("Invalid field 'ProlepticMonth' for get() method, use getLong() instead"); case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year); case YEAR: return year; case ERA: return (year >= 1 ? 1 : 0); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } - private long getEpochMonth() { - return ((year - 1970) * 12L) + (month - 1); + private long getProlepticMonth() { + return (year * 12L + month - 1); } //----------------------------------------------------------------------- @@ -626,7 +645,7 @@ * The {@code Chronology} represents the calendar system in use. * The ISO-8601 calendar system is the modern civil calendar system used today * in most of the world. It is equivalent to the proleptic Gregorian calendar - * system, in which todays's rules for leap years are applied for all time. + * system, in which today's rules for leap years are applied for all time. * * @return the ISO chronology, not null */ @@ -810,7 +829,7 @@ *

    * A simple adjuster might simply set the one of the fields, such as the year field. * A more complex adjuster might set the date to the last day of the month. - * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * A selection of common adjustments is provided in {@link TemporalAdjuster}. * These include finding the "last day of the month" and "next Wednesday". * Key date-time classes also implement the {@code TemporalAdjuster} interface, * such as {@link Month} and {@link java.time.MonthDay MonthDay}. @@ -908,8 +927,8 @@ * The year will be unchanged. The day-of-month will also be unchanged, * unless it would be invalid for the new month and year. In that case, the * day-of-month is adjusted to the maximum valid value for the new month and year. - *

  • {@code EPOCH_MONTH} - - * Returns a {@code LocalDate} with the specified epoch-month. + *
  • {@code PROLEPTIC_MONTH} - + * Returns a {@code LocalDate} with the specified proleptic-month. * The day-of-month will be unchanged, unless it would be invalid for the new month * and year. In that case, the day-of-month is adjusted to the maximum valid value * for the new month and year. @@ -933,7 +952,7 @@ * In all cases, if the new value is outside the valid range of values for the field * then a {@code DateTimeException} will be thrown. *

    - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -946,6 +965,7 @@ * @param newValue the new value of the field in the result * @return a {@code LocalDate} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -963,12 +983,12 @@ case ALIGNED_WEEK_OF_MONTH: return plusWeeks(newValue - getLong(ALIGNED_WEEK_OF_MONTH)); case ALIGNED_WEEK_OF_YEAR: return plusWeeks(newValue - getLong(ALIGNED_WEEK_OF_YEAR)); case MONTH_OF_YEAR: return withMonth((int) newValue); - case EPOCH_MONTH: return plusMonths(newValue - getLong(EPOCH_MONTH)); + case PROLEPTIC_MONTH: return plusMonths(newValue - getProlepticMonth()); case YEAR_OF_ERA: return withYear((int) (year >= 1 ? newValue : 1 - newValue)); case YEAR: return withYear((int) newValue); case ERA: return (getLong(ERA) == newValue ? this : withYear(1 - year)); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.adjustInto(this, newValue); } @@ -1137,7 +1157,7 @@ * valid value for the new month and year. * *

    - * All other {@code ChronoUnit} instances will throw a {@code DateTimeException}. + * All other {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoUnit}, then the result of this method * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} @@ -1150,6 +1170,7 @@ * @param unit the unit of the amount to add, not null * @return a {@code LocalDate} based on this date with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1166,7 +1187,7 @@ case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.addTo(this, amountToAdd); } @@ -1315,6 +1336,7 @@ * @param unit the unit of the amount to subtract, not null * @return a {@code LocalDate} based on this date with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1431,7 +1453,7 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.localDate()) { + if (query == TemporalQuery.localDate()) { return (R) this; } return ChronoLocalDate.super.query(query); @@ -1508,10 +1530,12 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this date and the end date * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override public long periodUntil(Temporal endDate, TemporalUnit unit) { + Objects.requireNonNull(unit, "unit"); if (endDate instanceof LocalDate == false) { Objects.requireNonNull(endDate, "endDate"); throw new DateTimeException("Unable to calculate period between objects of two different types"); @@ -1528,7 +1552,7 @@ case MILLENNIA: return monthsUntil(end) / 12000; case ERAS: return end.getLong(ERA) - getLong(ERA); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.between(this, endDate); } @@ -1538,8 +1562,8 @@ } private long monthsUntil(LocalDate end) { - long packed1 = getEpochMonth() * 32L + getDayOfMonth(); // no overflow - long packed2 = end.getEpochMonth() * 32L + end.getDayOfMonth(); // no overflow + long packed1 = getProlepticMonth() * 32L + getDayOfMonth(); // no overflow + long packed2 = end.getProlepticMonth() * 32L + end.getDayOfMonth(); // no overflow return (packed2 - packed1) / 32; } @@ -1549,6 +1573,7 @@ * This calculates the period between two dates in terms of years, months and days. * The start and end points are {@code this} and the specified date. * The result will be negative if the end is before the start. + * The negative sign will be the same in each of year, month and day. *

    * The calculation is performed using the ISO calendar system. * If necessary, the input date will be converted to ISO. @@ -1561,9 +1586,6 @@ * than or equal to the start day-of-month. * For example, from {@code 2010-01-15} to {@code 2011-03-18} is "1 year, 2 months and 3 days". *

    - * The result of this method can be a negative period if the end is before the start. - * The negative sign will be the same in each of year, month and day. - *

    * There are two equivalent ways of using this method. * The first is to invoke this method. * The second is to use {@link Period#between(LocalDate, LocalDate)}: @@ -1580,7 +1602,7 @@ @Override public Period periodUntil(ChronoLocalDate endDate) { LocalDate end = LocalDate.from(endDate); - long totalMonths = end.getEpochMonth() - this.getEpochMonth(); // safe + long totalMonths = end.getProlepticMonth() - this.getProlepticMonth(); // safe int days = end.day - this.day; if (totalMonths > 0 && days < 0) { totalMonths--; @@ -1595,6 +1617,21 @@ return Period.of(Math.toIntExact(years), months, days); } + /** + * Formats this date using the specified formatter. + *

    + * This date will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted date string, not null + * @throws DateTimeException if an error occurs during printing + */ + @Override // override for Javadoc and performance + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Combines this date with a time to create a {@code LocalDateTime}. @@ -1800,7 +1837,7 @@ * This method only considers the position of the two dates on the local time-line. * It does not take into account the chronology, or calendar system. * This is different from the comparison in {@link #compareTo(ChronoLocalDate)}, - * but is the same approach as {@link #DATE_COMPARATOR}. + * but is the same approach as {@link ChronoLocalDate#timeLineOrder()}. * * @param other the other date to compare to, not null * @return true if this date is after the specified date @@ -1829,7 +1866,7 @@ * This method only considers the position of the two dates on the local time-line. * It does not take into account the chronology, or calendar system. * This is different from the comparison in {@link #compareTo(ChronoLocalDate)}, - * but is the same approach as {@link #DATE_COMPARATOR}. + * but is the same approach as {@link ChronoLocalDate#timeLineOrder()}. * * @param other the other date to compare to, not null * @return true if this date is before the specified date @@ -1858,7 +1895,7 @@ * This method only considers the position of the two dates on the local time-line. * It does not take into account the chronology, or calendar system. * This is different from the comparison in {@link #compareTo(ChronoLocalDate)} - * but is the same approach as {@link #DATE_COMPARATOR}. + * but is the same approach as {@link ChronoLocalDate#timeLineOrder()}. * * @param other the other date to compare to, not null * @return true if this date is equal to the specified date @@ -1912,7 +1949,7 @@ /** * Outputs this date as a {@code String}, such as {@code 2007-12-03}. *

    - * The output will be in the ISO-8601 format {@code yyyy-MM-dd}. + * The output will be in the ISO-8601 format {@code uuuu-MM-dd}. * * @return a string representation of this date, not null */ @@ -1942,21 +1979,6 @@ .toString(); } - /** - * Outputs this date as a {@code String} using the formatter. - *

    - * This date will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted date string, not null - * @throws DateTimeException if an error occurs during printing - */ - @Override // override for Javadoc - public String toString(DateTimeFormatter formatter) { - return ChronoLocalDate.super.toString(formatter); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/LocalDateTime.java --- a/src/share/classes/java/time/LocalDateTime.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/LocalDateTime.java Sat Apr 13 21:51:36 2013 +0100 @@ -79,12 +79,10 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.time.chrono.ChronoLocalDateTime; -import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -92,6 +90,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.time.zone.ZoneRules; import java.util.Objects; @@ -546,7 +545,7 @@ *

  • {@code ALIGNED_WEEK_OF_MONTH} *
  • {@code ALIGNED_WEEK_OF_YEAR} *
  • {@code MONTH_OF_YEAR} - *
  • {@code EPOCH_MONTH} + *
  • {@code PROLEPTIC_MONTH} *
  • {@code YEAR_OF_ERA} *
  • {@code YEAR} *
  • {@code ERA} @@ -565,7 +564,7 @@ public boolean isSupported(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return f.isDateField() || f.isTimeField(); + return f.isDateBased() || f.isTimeBased(); } return field != null && field.isSupportedBy(this); } @@ -581,7 +580,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -591,12 +590,13 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return (f.isTimeField() ? time.range(field) : date.range(field)); + return (f.isTimeBased() ? time.range(field) : date.range(field)); } return field.rangeRefinedBy(this); } @@ -612,9 +612,9 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time, except {@code NANO_OF_DAY}, {@code MICRO_OF_DAY}, - * {@code EPOCH_DAY} and {@code EPOCH_MONTH} which are too large to fit in + * {@code EPOCH_DAY} and {@code PROLEPTIC_MONTH} which are too large to fit in * an {@code int} and throw a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -623,14 +623,17 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override public int get(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return (f.isTimeField() ? time.get(field) : date.get(field)); + return (f.isTimeBased() ? time.get(field) : date.get(field)); } return ChronoLocalDateTime.super.get(field); } @@ -645,7 +648,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -655,13 +658,14 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override public long getLong(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return (f.isTimeField() ? time.getLong(field) : date.getLong(field)); + return (f.isTimeBased() ? time.getLong(field) : date.getLong(field)); } return field.getFrom(this); } @@ -822,7 +826,7 @@ *

    * A simple adjuster might simply set the one of the fields, such as the year field. * A more complex adjuster might set the date to the last day of the month. - * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * A selection of common adjustments is provided in {@link TemporalAdjuster}. * These include finding the "last day of the month" and "next Wednesday". * Key date-time classes also implement the {@code TemporalAdjuster} interface, * such as {@link Month} and {@link java.time.MonthDay MonthDay}. @@ -886,7 +890,7 @@ * The {@link #isSupported(TemporalField) supported fields} will behave as per * the matching method on {@link LocalDate#with(TemporalField, long) LocalDate} * or {@link LocalTime#with(TemporalField, long) LocalTime}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -899,13 +903,14 @@ * @param newValue the new value of the field in the result * @return a {@code LocalDateTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override public LocalDateTime with(TemporalField field, long newValue) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - if (f.isTimeField()) { + if (f.isTimeBased()) { return with(date, time.with(field, newValue)); } else { return with(date.with(field, newValue), time); @@ -1052,6 +1057,7 @@ * @param unit the unit to truncate to, not null * @return a {@code LocalDateTime} based on this date-time with the time truncated, not null * @throws DateTimeException if unable to truncate + * @throws UnsupportedTemporalTypeException if the field is not supported */ public LocalDateTime truncatedTo(TemporalUnit unit) { return with(date, time.truncatedTo(unit)); @@ -1106,6 +1112,7 @@ * @param unit the unit of the amount to add, not null * @return a {@code LocalDateTime} based on this date-time with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1312,6 +1319,7 @@ * @param unit the unit of the amount to subtract, not null * @return a {@code LocalDateTime} based on this date-time with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1517,7 +1525,7 @@ @SuppressWarnings("unchecked") @Override // override for Javadoc public R query(TemporalQuery query) { - if (query == Queries.localDate()) { + if (query == TemporalQuery.localDate()) { return (R) date; } return ChronoLocalDateTime.super.query(query); @@ -1597,6 +1605,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this date-time and the end date-time * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1610,31 +1619,79 @@ ChronoUnit f = (ChronoUnit) unit; if (f.isTimeUnit()) { long amount = date.daysUntil(end.date); + if (amount == 0) { + return time.periodUntil(end.time, unit); + } + long timePart = end.time.toNanoOfDay() - time.toNanoOfDay(); + if (amount > 0) { + amount--; // safe + timePart += NANOS_PER_DAY; // safe + } else { + amount++; // safe + timePart -= NANOS_PER_DAY; // safe + } switch (f) { - case NANOS: amount = Math.multiplyExact(amount, NANOS_PER_DAY); break; - case MICROS: amount = Math.multiplyExact(amount, MICROS_PER_DAY); break; - case MILLIS: amount = Math.multiplyExact(amount, MILLIS_PER_DAY); break; - case SECONDS: amount = Math.multiplyExact(amount, SECONDS_PER_DAY); break; - case MINUTES: amount = Math.multiplyExact(amount, MINUTES_PER_DAY); break; - case HOURS: amount = Math.multiplyExact(amount, HOURS_PER_DAY); break; - case HALF_DAYS: amount = Math.multiplyExact(amount, 2); break; + case NANOS: + amount = Math.multiplyExact(amount, NANOS_PER_DAY); + break; + case MICROS: + amount = Math.multiplyExact(amount, MICROS_PER_DAY); + timePart = timePart / 1000; + break; + case MILLIS: + amount = Math.multiplyExact(amount, MILLIS_PER_DAY); + timePart = timePart / 1_000_000; + break; + case SECONDS: + amount = Math.multiplyExact(amount, SECONDS_PER_DAY); + timePart = timePart / NANOS_PER_SECOND; + break; + case MINUTES: + amount = Math.multiplyExact(amount, MINUTES_PER_DAY); + timePart = timePart / NANOS_PER_MINUTE; + break; + case HOURS: + amount = Math.multiplyExact(amount, HOURS_PER_DAY); + timePart = timePart / NANOS_PER_HOUR; + break; + case HALF_DAYS: + amount = Math.multiplyExact(amount, 2); + timePart = timePart / (NANOS_PER_HOUR * 12); + break; } - return Math.addExact(amount, time.periodUntil(end.time, unit)); + return Math.addExact(amount, timePart); } LocalDate endDate = end.date; - if (end.time.isBefore(time)) { + if (endDate.isAfter(date) && end.time.isBefore(time)) { endDate = endDate.minusDays(1); + } else if (endDate.isBefore(date) && end.time.isAfter(time)) { + endDate = endDate.plusDays(1); } return date.periodUntil(endDate, unit); } return unit.between(this, endDateTime); } + /** + * Formats this date-time using the specified formatter. + *

    + * This date-time will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + @Override // override for Javadoc and performance + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** - * Combines this time with a date to create an {@code OffsetTime}. + * Combines this date-time with an offset to create an {@code OffsetDateTime}. *

    - * This returns an {@code OffsetTime} formed from this time at the specified offset. + * This returns an {@code OffsetDateTime} formed from this date-time at the specified offset. * All possible combinations of date-time and offset are valid. * * @param offset the offset to combine with, not null @@ -1645,7 +1702,7 @@ } /** - * Combines this time with a time-zone to create a {@code ZonedDateTime}. + * Combines this date-time with a time-zone to create a {@code ZonedDateTime}. *

    * This returns a {@code ZonedDateTime} formed from this date-time at the * specified time-zone. The result will match this date-time as closely as possible. @@ -1725,7 +1782,7 @@ * This method only considers the position of the two date-times on the local time-line. * It does not take into account the chronology, or calendar system. * This is different from the comparison in {@link #compareTo(ChronoLocalDateTime)}, - * but is the same approach as {@link #DATE_TIME_COMPARATOR}. + * but is the same approach as {@link ChronoLocalDateTime#timeLineOrder()}. * * @param other the other date-time to compare to, not null * @return true if this date-time is after the specified date-time @@ -1754,7 +1811,7 @@ * This method only considers the position of the two date-times on the local time-line. * It does not take into account the chronology, or calendar system. * This is different from the comparison in {@link #compareTo(ChronoLocalDateTime)}, - * but is the same approach as {@link #DATE_TIME_COMPARATOR}. + * but is the same approach as {@link ChronoLocalDateTime#timeLineOrder()}. * * @param other the other date-time to compare to, not null * @return true if this date-time is before the specified date-time @@ -1783,7 +1840,7 @@ * This method only considers the position of the two date-times on the local time-line. * It does not take into account the chronology, or calendar system. * This is different from the comparison in {@link #compareTo(ChronoLocalDateTime)}, - * but is the same approach as {@link #DATE_TIME_COMPARATOR}. + * but is the same approach as {@link ChronoLocalDateTime#timeLineOrder()}. * * @param other the other date-time to compare to, not null * @return true if this date-time is equal to the specified date-time @@ -1834,11 +1891,11 @@ *

    * The output will be one of the following ISO-8601 formats: *

      - *
    • {@code yyyy-MM-dd'T'HH:mm}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ss}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ss.SSS}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSS}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ss}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ss.SSS}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ss.SSSSSS}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS}
    • *

    * The format used will be the shortest that outputs the full value of * the time where the omitted parts are implied to be zero. @@ -1850,21 +1907,6 @@ return date.toString() + 'T' + time.toString(); } - /** - * Outputs this date-time as a {@code String} using the formatter. - *

    - * This date-time will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted date-time string, not null - * @throws DateTimeException if an error occurs during printing - */ - @Override // override for Javadoc - public String toString(DateTimeFormatter formatter) { - return ChronoLocalDateTime.super.toString(formatter); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/LocalTime.java --- a/src/share/classes/java/time/LocalTime.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/LocalTime.java Sat Apr 13 21:51:36 2013 +0100 @@ -80,7 +80,6 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -88,6 +87,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; @@ -383,7 +383,7 @@ * A {@code TemporalAccessor} represents an arbitrary set of date and time information, * which this factory converts to an instance of {@code LocalTime}. *

    - * The conversion uses the {@link Queries#localTime()} query, which relies + * The conversion uses the {@link TemporalQuery#localTime()} query, which relies * on extracting the {@link ChronoField#NANO_OF_DAY NANO_OF_DAY} field. *

    * This method matches the signature of the functional interface {@link TemporalQuery} @@ -394,7 +394,7 @@ * @throws DateTimeException if unable to convert to a {@code LocalTime} */ public static LocalTime from(TemporalAccessor temporal) { - LocalTime time = temporal.query(Queries.localTime()); + LocalTime time = temporal.query(TemporalQuery.localTime()); if (time == null) { throw new DateTimeException("Unable to obtain LocalTime from TemporalAccessor: " + temporal.getClass()); } @@ -505,7 +505,7 @@ @Override public boolean isSupported(TemporalField field) { if (field instanceof ChronoField) { - return ((ChronoField) field).isTimeField(); + return field.isTimeBased(); } return field != null && field.isSupportedBy(this); } @@ -521,7 +521,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -531,6 +531,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override // override for Javadoc public ValueRange range(TemporalField field) { @@ -549,7 +550,7 @@ * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this time, except {@code NANO_OF_DAY} and {@code MICRO_OF_DAY} * which are too large to fit in an {@code int} and throw a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -558,7 +559,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc and performance @@ -579,7 +583,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this time. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -589,6 +593,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -608,9 +613,9 @@ private int get0(TemporalField field) { switch ((ChronoField) field) { case NANO_OF_SECOND: return nano; - case NANO_OF_DAY: throw new DateTimeException("Field too large for an int: " + field); + case NANO_OF_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'NanoOfDay' for get() method, use getLong() instead"); case MICRO_OF_SECOND: return nano / 1000; - case MICRO_OF_DAY: throw new DateTimeException("Field too large for an int: " + field); + case MICRO_OF_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'MicroOfDay' for get() method, use getLong() instead"); case MILLI_OF_SECOND: return nano / 1000_000; case MILLI_OF_DAY: return (int) (toNanoOfDay() / 1000_000); case SECOND_OF_MINUTE: return second; @@ -623,7 +628,7 @@ case CLOCK_HOUR_OF_DAY: return (hour == 0 ? 24 : hour); case AMPM_OF_DAY: return hour / 12; } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } //----------------------------------------------------------------------- @@ -760,7 +765,7 @@ * In all cases, if the new value is outside the valid range of values for the field * then a {@code DateTimeException} will be thrown. *

    - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -773,6 +778,7 @@ * @param newValue the new value of the field in the result * @return a {@code LocalTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -797,7 +803,7 @@ case CLOCK_HOUR_OF_DAY: return withHour((int) (newValue == 24 ? 0 : newValue)); case AMPM_OF_DAY: return plusHours((newValue - (hour / 12)) * 12); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.adjustInto(this, newValue); } @@ -890,6 +896,7 @@ * @param unit the unit to truncate to, not null * @return a {@code LocalTime} based on this time with the time truncated, not null * @throws DateTimeException if unable to truncate + * @throws UnsupportedTemporalTypeException if the unit is not supported */ public LocalTime truncatedTo(TemporalUnit unit) { if (unit == ChronoUnit.NANOS) { @@ -897,11 +904,11 @@ } Duration unitDur = unit.getDuration(); if (unitDur.getSeconds() > SECONDS_PER_DAY) { - throw new DateTimeException("Unit is too large to be used for truncation"); + throw new UnsupportedTemporalTypeException("Unit is too large to be used for truncation"); } long dur = unitDur.toNanos(); if ((NANOS_PER_DAY % dur) != 0) { - throw new DateTimeException("Unit must divide into a standard day without remainder"); + throw new UnsupportedTemporalTypeException("Unit must divide into a standard day without remainder"); } long nod = toNanoOfDay(); return ofNanoOfDay((nod / dur) * dur); @@ -972,7 +979,7 @@ * This returns {@code this} time. * *

    - * All other {@code ChronoUnit} instances will throw a {@code DateTimeException}. + * All other {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoUnit}, then the result of this method * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} @@ -985,6 +992,7 @@ * @param unit the unit of the amount to add, not null * @return a {@code LocalTime} based on this time with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1001,7 +1009,7 @@ case HALF_DAYS: return plusHours((amountToAdd % 2) * 12); case DAYS: return this; } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.addTo(this, amountToAdd); } @@ -1147,6 +1155,7 @@ * @param unit the unit of the amount to subtract, not null * @return a {@code LocalTime} based on this time with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1237,13 +1246,14 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.chronology() || query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + if (query == TemporalQuery.chronology() || query == TemporalQuery.zoneId() || + query == TemporalQuery.zone() || query == TemporalQuery.offset()) { return null; - } else if (query == Queries.localTime()) { + } else if (query == TemporalQuery.localTime()) { return (R) this; - } else if (query == Queries.localDate()) { + } else if (query == TemporalQuery.localDate()) { return null; - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) NANOS; } // inline TemporalAccessor.super.query(query) as an optimization @@ -1322,6 +1332,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this time and the end time * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1342,11 +1353,25 @@ case HOURS: return nanosUntil / NANOS_PER_HOUR; case HALF_DAYS: return nanosUntil / (12 * NANOS_PER_HOUR); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.between(this, endTime); } + /** + * Formats this time using the specified formatter. + *

    + * This time will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Combines this time with a date to create a {@code LocalDateTime}. @@ -1362,7 +1387,7 @@ } /** - * Combines this time with a date to create an {@code OffsetTime}. + * Combines this time with an offset to create an {@code OffsetTime}. *

    * This returns an {@code OffsetTime} formed from this time at the specified offset. * All possible combinations of time and offset are valid. @@ -1533,21 +1558,6 @@ return buf.toString(); } - /** - * Outputs this time as a {@code String} using the formatter. - *

    - * This time will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted time string, not null - * @throws DateTimeException if an error occurs during printing - */ - public String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/Month.java --- a/src/share/classes/java/time/Month.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/Month.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,6 +61,7 @@ */ package java.time; +import java.time.temporal.UnsupportedTemporalTypeException; import static java.time.temporal.ChronoField.MONTH_OF_YEAR; import static java.time.temporal.ChronoUnit.MONTHS; @@ -69,7 +70,6 @@ import java.time.format.DateTimeFormatterBuilder; import java.time.format.TextStyle; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -290,7 +290,7 @@ *

    * If the field is {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} then the * range of the month-of-year, from 1 to 12, will be returned. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -300,6 +300,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -319,7 +320,7 @@ *

    * If the field is {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} then the * value of the month-of-year, from 1 to 12, will be returned. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -328,7 +329,10 @@ * * @param field the field to get, not null * @return the value for the field, within the valid range of values - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -348,7 +352,7 @@ *

    * If the field is {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} then the * value of the month-of-year, from 1 to 12, will be returned. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -358,6 +362,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -365,7 +370,7 @@ if (field == MONTH_OF_YEAR) { return getValue(); } else if (field instanceof ChronoField) { - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } @@ -554,9 +559,9 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.chronology()) { + if (query == TemporalQuery.chronology()) { return (R) IsoChronology.INSTANCE; - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) MONTHS; } return TemporalAccessor.super.query(query); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/MonthDay.java --- a/src/share/classes/java/time/MonthDay.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/MonthDay.java Sat Apr 13 21:51:36 2013 +0100 @@ -76,12 +76,12 @@ import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; @@ -354,7 +354,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -364,6 +364,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -386,7 +387,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this month-day. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -395,7 +396,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc @@ -413,7 +417,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this month-day. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -423,6 +427,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -433,7 +438,7 @@ case DAY_OF_MONTH: return day; case MONTH_OF_YEAR: return month; } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } @@ -575,7 +580,7 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.chronology()) { + if (query == TemporalQuery.chronology()) { return (R) IsoChronology.INSTANCE; } return TemporalAccessor.super.query(query); @@ -617,6 +622,20 @@ return temporal.with(DAY_OF_MONTH, Math.min(temporal.range(DAY_OF_MONTH).getMaximum(), day)); } + /** + * Formats this month-day using the specified formatter. + *

    + * This month-day will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted month-day string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Combines this month-day with a year to create a {@code LocalDate}. @@ -722,21 +741,6 @@ .toString(); } - /** - * Outputs this month-day as a {@code String} using the formatter. - *

    - * This month-day will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted month-day string, not null - * @throws DateTimeException if an error occurs during printing - */ - public String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/OffsetDateTime.java --- a/src/share/classes/java/time/OffsetDateTime.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/OffsetDateTime.java Sat Apr 13 21:51:36 2013 +0100 @@ -78,7 +78,6 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -86,6 +85,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.time.zone.ZoneRules; import java.util.Comparator; @@ -436,7 +436,7 @@ *

  • {@code ALIGNED_WEEK_OF_MONTH} *
  • {@code ALIGNED_WEEK_OF_YEAR} *
  • {@code MONTH_OF_YEAR} - *
  • {@code EPOCH_MONTH} + *
  • {@code PROLEPTIC_MONTH} *
  • {@code YEAR_OF_ERA} *
  • {@code YEAR} *
  • {@code ERA} @@ -469,7 +469,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -479,6 +479,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -502,9 +503,9 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time, except {@code NANO_OF_DAY}, {@code MICRO_OF_DAY}, - * {@code EPOCH_DAY}, {@code EPOCH_MONTH} and {@code INSTANT_SECONDS} which are too + * {@code EPOCH_DAY}, {@code PROLEPTIC_MONTH} and {@code INSTANT_SECONDS} which are too * large to fit in an {@code int} and throw a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -513,15 +514,20 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override public int get(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { - case INSTANT_SECONDS: throw new DateTimeException("Field too large for an int: " + field); - case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + case INSTANT_SECONDS: + throw new UnsupportedTemporalTypeException("Invalid field 'InstantSeconds' for get() method, use getLong() instead"); + case OFFSET_SECONDS: + return getOffset().getTotalSeconds(); } return dateTime.get(field); } @@ -538,7 +544,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -548,6 +554,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -790,7 +797,7 @@ *

    * A simple adjuster might simply set the one of the fields, such as the year field. * A more complex adjuster might set the date to the last day of the month. - * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * A selection of common adjustments is provided in {@link TemporalAdjuster}. * These include finding the "last day of the month" and "next Wednesday". * Key date-time classes also implement the {@code TemporalAdjuster} interface, * such as {@link Month} and {@link java.time.MonthDay MonthDay}. @@ -867,7 +874,7 @@ * the matching method on {@link LocalDateTime#with(TemporalField, long) LocalDateTime}. * In this case, the offset is not part of the calculation and will be unchanged. *

    - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -880,6 +887,7 @@ * @param newValue the new value of the field in the result * @return an {@code OffsetDateTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1041,6 +1049,7 @@ * @param unit the unit to truncate to, not null * @return an {@code OffsetDateTime} based on this date-time with the time truncated, not null * @throws DateTimeException if unable to truncate + * @throws UnsupportedTemporalTypeException if the unit is not supported */ public OffsetDateTime truncatedTo(TemporalUnit unit) { return with(dateTime.truncatedTo(unit), offset); @@ -1094,6 +1103,7 @@ * @param unit the unit of the amount to add, not null * @return an {@code OffsetDateTime} based on this date-time with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1285,6 +1295,7 @@ * @param unit the unit of the amount to subtract, not null * @return an {@code OffsetDateTime} based on this date-time with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1453,17 +1464,17 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.offset() || query == Queries.zone()) { + if (query == TemporalQuery.offset() || query == TemporalQuery.zone()) { return (R) getOffset(); - } else if (query == Queries.zoneId()) { + } else if (query == TemporalQuery.zoneId()) { return null; - } else if (query == Queries.localDate()) { + } else if (query == TemporalQuery.localDate()) { return (R) toLocalDate(); - } else if (query == Queries.localTime()) { + } else if (query == TemporalQuery.localTime()) { return (R) toLocalTime(); - } else if (query == Queries.chronology()) { + } else if (query == TemporalQuery.chronology()) { return (R) IsoChronology.INSTANCE; - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) NANOS; } // inline TemporalAccessor.super.query(query) as an optimization @@ -1479,8 +1490,8 @@ * with the offset, date and time changed to be the same as this. *

    * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} - * three times, passing {@link ChronoField#OFFSET_SECONDS}, - * {@link ChronoField#EPOCH_DAY} and {@link ChronoField#NANO_OF_DAY} as the fields. + * three times, passing {@link ChronoField#EPOCH_DAY}, + * {@link ChronoField#NANO_OF_DAY} and {@link ChronoField#OFFSET_SECONDS} as the fields. *

    * In most cases, it is clearer to reverse the calling pattern by using * {@link Temporal#with(TemporalAdjuster)}: @@ -1499,10 +1510,14 @@ */ @Override public Temporal adjustInto(Temporal temporal) { + // OffsetDateTime is treated as three separate fields, not an instant + // this produces the most consistent set of results overall + // the offset is set after the date and time, as it is typically a small + // tweak to the result, with ZonedDateTime frequently ignoring the offset return temporal - .with(OFFSET_SECONDS, getOffset().getTotalSeconds()) .with(EPOCH_DAY, toLocalDate().toEpochDay()) - .with(NANO_OF_DAY, toLocalTime().toNanoOfDay()); + .with(NANO_OF_DAY, toLocalTime().toNanoOfDay()) + .with(OFFSET_SECONDS, getOffset().getTotalSeconds()); } /** @@ -1552,6 +1567,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this date-time and the end date-time * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1568,6 +1584,20 @@ return unit.between(this, endDateTime); } + /** + * Formats this date-time using the specified formatter. + *

    + * This date-time will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Combines this date-time with a time-zone to create a {@code ZonedDateTime} @@ -1796,11 +1826,11 @@ *

    * The output will be one of the following ISO-8601 formats: *

      - *
    • {@code yyyy-MM-dd'T'HH:mmXXXXX}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ssXXXXX}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXXXX}
    • - *
    • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXXXX}
    • + *
    • {@code uuuu-MM-dd'T'HH:mmXXXXX}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ssXXXXX}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ss.SSSXXXXX}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ss.SSSSSSXXXXX}
    • + *
    • {@code uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSXXXXX}
    • *

    * The format used will be the shortest that outputs the full value of * the time where the omitted parts are implied to be zero. @@ -1812,21 +1842,6 @@ return dateTime.toString() + offset.toString(); } - /** - * Outputs this date-time as a {@code String} using the formatter. - *

    - * This date-time will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted date-time string, not null - * @throws DateTimeException if an error occurs during printing - */ - public String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/OffsetTime.java --- a/src/share/classes/java/time/OffsetTime.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/OffsetTime.java Sat Apr 13 21:51:36 2013 +0100 @@ -79,7 +79,6 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -87,6 +86,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.time.zone.ZoneRules; import java.util.Objects; @@ -384,7 +384,7 @@ @Override public boolean isSupported(TemporalField field) { if (field instanceof ChronoField) { - return ((ChronoField) field).isTimeField() || field == OFFSET_SECONDS; + return field.isTimeBased() || field == OFFSET_SECONDS; } return field != null && field.isSupportedBy(this); } @@ -400,7 +400,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -410,6 +410,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -434,7 +435,7 @@ * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this time, except {@code NANO_OF_DAY} and {@code MICRO_OF_DAY} * which are too large to fit in an {@code int} and throw a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -443,7 +444,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc @@ -461,7 +465,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this time. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -471,6 +475,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -655,7 +660,7 @@ * the matching method on {@link LocalTime#with(TemporalField, long)} LocalTime}. * In this case, the offset is not part of the calculation and will be unchanged. *

    - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -668,6 +673,7 @@ * @param newValue the new value of the field in the result * @return an {@code OffsetTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -764,6 +770,7 @@ * @param unit the unit to truncate to, not null * @return an {@code OffsetTime} based on this time with the time truncated, not null * @throws DateTimeException if unable to truncate + * @throws UnsupportedTemporalTypeException if the unit is not supported */ public OffsetTime truncatedTo(TemporalUnit unit) { return with(time.truncatedTo(unit), offset); @@ -817,6 +824,7 @@ * @param unit the unit of the amount to add, not null * @return an {@code OffsetTime} based on this time with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -930,6 +938,7 @@ * @param unit the unit of the amount to subtract, not null * @return an {@code OffsetTime} based on this time with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1020,13 +1029,13 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.offset() || query == Queries.zone()) { + if (query == TemporalQuery.offset() || query == TemporalQuery.zone()) { return (R) offset; - } else if (query == Queries.zoneId() | query == Queries.chronology() || query == Queries.localDate()) { + } else if (query == TemporalQuery.zoneId() | query == TemporalQuery.chronology() || query == TemporalQuery.localDate()) { return null; - } else if (query == Queries.localTime()) { + } else if (query == TemporalQuery.localTime()) { return (R) time; - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) NANOS; } // inline TemporalAccessor.super.query(query) as an optimization @@ -1042,8 +1051,8 @@ * with the offset and time changed to be the same as this. *

    * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} - * twice, passing {@link ChronoField#OFFSET_SECONDS} and - * {@link ChronoField#NANO_OF_DAY} as the fields. + * twice, passing {@link ChronoField#NANO_OF_DAY} and + * {@link ChronoField#OFFSET_SECONDS} as the fields. *

    * In most cases, it is clearer to reverse the calling pattern by using * {@link Temporal#with(TemporalAdjuster)}: @@ -1063,8 +1072,8 @@ @Override public Temporal adjustInto(Temporal temporal) { return temporal - .with(OFFSET_SECONDS, offset.getTotalSeconds()) - .with(NANO_OF_DAY, time.toNanoOfDay()); + .with(NANO_OF_DAY, time.toNanoOfDay()) + .with(OFFSET_SECONDS, offset.getTotalSeconds()); } /** @@ -1112,6 +1121,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this time and the end time * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1132,14 +1142,28 @@ case HOURS: return nanosUntil / NANOS_PER_HOUR; case HALF_DAYS: return nanosUntil / (12 * NANOS_PER_HOUR); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.between(this, endTime); } + /** + * Formats this time using the specified formatter. + *

    + * This time will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** - * Combines this date with a time to create an {@code OffsetDateTime}. + * Combines this time with a date to create an {@code OffsetDateTime}. *

    * This returns an {@code OffsetDateTime} formed from this time and the specified date. * All possible combinations of date and time are valid. @@ -1307,27 +1331,12 @@ return time.toString() + offset.toString(); } - /** - * Outputs this time as a {@code String} using the formatter. - *

    - * This time will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted time string, not null - * @throws DateTimeException if an error occurs during printing - */ - public String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - - // ----------------------------------------------------------------------- + //----------------------------------------------------------------------- /** * Writes the object using a * dedicated serialized form. *

    -     *  out.writeByte(9);  // identifies this as a OffsetDateTime
    +     *  out.writeByte(9);  // identifies this as a OffsetTime
          *  out.writeObject(time);
          *  out.writeObject(offset);
          * 
    diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/Period.java --- a/src/share/classes/java/time/Period.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/Period.java Sat Apr 13 21:51:36 2013 +0100 @@ -79,6 +79,7 @@ import java.time.temporal.Temporal; import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Arrays; import java.util.Collections; @@ -215,26 +216,40 @@ //----------------------------------------------------------------------- /** - * Obtains a {@code Period} consisting of the number of years, months, - * and days between two dates. + * Obtains an instance of {@code Period} from a temporal amount. *

    - * The start date is included, but the end date is not. - * The period is calculated by removing complete months, then calculating - * the remaining number of days, adjusting to ensure that both have the same sign. - * The number of months is then split into years and months based on a 12 month year. - * A month is considered if the end day-of-month is greater than or equal to the start day-of-month. - * For example, from {@code 2010-01-15} to {@code 2011-03-18} is one year, two months and three days. + * This obtains a period based on the specified amount. + * A {@code TemporalAmount} represents an amount of time, which may be + * date-based or time-based, which this factory extracts to a period. *

    - * The result of this method can be a negative period if the end is before the start. - * The negative sign will be the same in each of year, month and day. + * The conversion loops around the set of units from the amount and uses + * the {@link ChronoUnit#YEARS YEARS}, {@link ChronoUnit#MONTHS MONTHS} + * and {@link ChronoUnit#DAYS DAYS} units to create a period. + * If any other units are found then an exception is thrown. * - * @param startDate the start date, inclusive, not null - * @param endDate the end date, exclusive, not null - * @return the period between this date and the end date, not null - * @see ChronoLocalDate#periodUntil(ChronoLocalDate) + * @param amount the temporal amount to convert, not null + * @return the equivalent period, not null + * @throws DateTimeException if unable to convert to a {@code Period} + * @throws ArithmeticException if the amount of years, months or days exceeds an int */ - public static Period between(LocalDate startDate, LocalDate endDate) { - return startDate.periodUntil(endDate); + public static Period from(TemporalAmount amount) { + Objects.requireNonNull(amount, "amount"); + int years = 0; + int months = 0; + int days = 0; + for (TemporalUnit unit : amount.getUnits()) { + long unitAmount = amount.get(unit); + if (unit == ChronoUnit.YEARS) { + years = Math.toIntExact(unitAmount); + } else if (unit == ChronoUnit.MONTHS) { + months = Math.toIntExact(unitAmount); + } else if (unit == ChronoUnit.DAYS) { + days = Math.toIntExact(unitAmount); + } else { + throw new DateTimeException("Unit must be Years, Months or Days, but was " + unit); + } + } + return create(years, months, days); } //----------------------------------------------------------------------- @@ -298,6 +313,30 @@ //----------------------------------------------------------------------- /** + * Obtains a {@code Period} consisting of the number of years, months, + * and days between two dates. + *

    + * The start date is included, but the end date is not. + * The period is calculated by removing complete months, then calculating + * the remaining number of days, adjusting to ensure that both have the same sign. + * The number of months is then split into years and months based on a 12 month year. + * A month is considered if the end day-of-month is greater than or equal to the start day-of-month. + * For example, from {@code 2010-01-15} to {@code 2011-03-18} is one year, two months and three days. + *

    + * The result of this method can be a negative period if the end is before the start. + * The negative sign will be the same in each of year, month and day. + * + * @param startDate the start date, inclusive, not null + * @param endDate the end date, exclusive, not null + * @return the period between this date and the end date, not null + * @see ChronoLocalDate#periodUntil(ChronoLocalDate) + */ + public static Period between(LocalDate startDate, LocalDate endDate) { + return startDate.periodUntil(endDate); + } + + //----------------------------------------------------------------------- + /** * Creates an instance. * * @param years the amount @@ -336,6 +375,7 @@ * @param unit the {@code TemporalUnit} for which to return the value * @return the long value of the unit * @throws DateTimeException if the unit is not supported + * @throws UnsupportedTemporalTypeException if the unit is not supported */ @Override public long get(TemporalUnit unit) { @@ -346,7 +386,7 @@ } else if (unit == ChronoUnit.DAYS) { return getDays(); } else { - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } } @@ -499,8 +539,7 @@ /** * Returns a copy of this period with the specified period added. *

    - * This operates separately on the years, months, days and the normalized time. - * There is no further normalization beyond the normalized time. + * This operates separately on the years, months and days. *

    * For example, "1 year, 6 months and 3 days" plus "2 years, 2 months and 2 days" * returns "3 years, 8 months and 5 days". @@ -582,8 +621,7 @@ /** * Returns a copy of this period with the specified period subtracted. *

    - * This operates separately on the years, months, days and the normalized time. - * There is no further normalization beyond the normalized time. + * This operates separately on the years, months and days. *

    * For example, "1 year, 6 months and 3 days" minus "2 years, 2 months and 2 days" * returns "-1 years, 4 months and 1 day". @@ -845,9 +883,11 @@ * @return the month range, negative if not fixed range */ private long monthRange(Temporal temporal) { - ValueRange startRange = Chronology.from(temporal).range(MONTH_OF_YEAR); - if (startRange.isFixed() && startRange.isIntValue()) { - return startRange.getMaximum() - startRange.getMinimum() + 1; + if (temporal.isSupported(MONTH_OF_YEAR)) { + ValueRange startRange = Chronology.from(temporal).range(MONTH_OF_YEAR); + if (startRange.isFixed() && startRange.isIntValue()) { + return startRange.getMaximum() - startRange.getMinimum() + 1; + } } return -1; } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/Year.java --- a/src/share/classes/java/time/Year.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/Year.java Sat Apr 13 21:51:36 2013 +0100 @@ -80,7 +80,6 @@ import java.time.format.SignStyle; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -88,6 +87,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; @@ -368,7 +368,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -378,6 +378,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -398,7 +399,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this year. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -407,7 +408,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc @@ -425,7 +429,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this year. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -435,6 +439,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -445,7 +450,7 @@ case YEAR: return year; case ERA: return (year < 1 ? 0 : 1); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } @@ -544,7 +549,7 @@ * In all cases, if the new value is outside the valid range of values for the field * then a {@code DateTimeException} will be thrown. *

    - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -557,6 +562,7 @@ * @param newValue the new value of the field in the result * @return a {@code Year} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -569,7 +575,7 @@ case YEAR: return Year.of((int) newValue); case ERA: return (getLong(ERA) == newValue ? this : Year.of(1 - year)); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.adjustInto(this, newValue); } @@ -632,7 +638,7 @@ * is unchanged. * *

    - * All other {@code ChronoUnit} instances will throw a {@code DateTimeException}. + * All other {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoUnit}, then the result of this method * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} @@ -645,6 +651,7 @@ * @param unit the unit of the amount to add, not null * @return a {@code Year} based on this year with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -657,7 +664,7 @@ case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.addTo(this, amountToAdd); } @@ -705,9 +712,9 @@ } /** - * Returns a copy of this year-month with the specified amount subtracted. + * Returns a copy of this year with the specified amount subtracted. *

    - * This returns a {@code YearMonth}, based on this one, with the amount + * This returns a {@code Year}, based on this one, with the amount * in terms of the unit subtracted. If it is not possible to subtract the amount, * because the unit is not supported or for some other reason, an exception is thrown. *

    @@ -718,8 +725,9 @@ * * @param amountToSubtract the amount of the unit to subtract from the result, may be negative * @param unit the unit of the amount to subtract, not null - * @return a {@code YearMonth} based on this year-month with the specified amount subtracted, not null + * @return a {@code Year} based on this year with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -762,9 +770,9 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.chronology()) { + if (query == TemporalQuery.chronology()) { return (R) IsoChronology.INSTANCE; - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) YEARS; } return Temporal.super.query(query); @@ -846,6 +854,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this year and the end year * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -864,11 +873,25 @@ case MILLENNIA: return yearsUntil / 1000; case ERAS: return end.getLong(ERA) - getLong(ERA); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.between(this, endYear); } + /** + * Formats this year using the specified formatter. + *

    + * This year will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted year string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Combines this year with a day-of-year to create a {@code LocalDate}. @@ -1014,21 +1037,6 @@ return Integer.toString(year); } - /** - * Outputs this year as a {@code String} using the formatter. - *

    - * This year will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted year string, not null - * @throws DateTimeException if an error occurs during printing - */ - public String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/YearMonth.java --- a/src/share/classes/java/time/YearMonth.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/YearMonth.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,9 +61,9 @@ */ package java.time; -import static java.time.temporal.ChronoField.EPOCH_MONTH; import static java.time.temporal.ChronoField.ERA; import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; import static java.time.temporal.ChronoField.YEAR; import static java.time.temporal.ChronoField.YEAR_OF_ERA; import static java.time.temporal.ChronoUnit.MONTHS; @@ -82,7 +82,6 @@ import java.time.format.SignStyle; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -90,6 +89,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; @@ -255,7 +255,7 @@ * Obtains an instance of {@code YearMonth} from a text string such as {@code 2007-12}. *

    * The string must represent a valid year-month. - * The format must be {@code yyyy-MM}. + * The format must be {@code uuuu-MM}. * Years outside the range 0000 to 9999 must be prefixed by the plus or minus symbol. * * @param text the text to parse such as "2007-12", not null @@ -320,7 +320,7 @@ * The supported fields are: *

      *
    • {@code MONTH_OF_YEAR} - *
    • {@code EPOCH_MONTH} + *
    • {@code PROLEPTIC_MONTH} *
    • {@code YEAR_OF_ERA} *
    • {@code YEAR} *
    • {@code ERA} @@ -339,7 +339,7 @@ public boolean isSupported(TemporalField field) { if (field instanceof ChronoField) { return field == YEAR || field == MONTH_OF_YEAR || - field == EPOCH_MONTH || field == YEAR_OF_ERA || field == ERA; + field == PROLEPTIC_MONTH || field == YEAR_OF_ERA || field == ERA; } return field != null && field.isSupportedBy(this); } @@ -355,7 +355,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

      * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -365,6 +365,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -384,9 +385,9 @@ *

      * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid - * values based on this year-month, except {@code EPOCH_MONTH} which is too + * values based on this year-month, except {@code PROLEPTIC_MONTH} which is too * large to fit in an {@code int} and throw a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

      * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -395,7 +396,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc @@ -413,7 +417,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this year-month. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

      * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -423,6 +427,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -430,18 +435,18 @@ if (field instanceof ChronoField) { switch ((ChronoField) field) { case MONTH_OF_YEAR: return month; - case EPOCH_MONTH: return getEpochMonth(); + case PROLEPTIC_MONTH: return getProlepticMonth(); case YEAR_OF_ERA: return (year < 1 ? 1 - year : year); case YEAR: return year; case ERA: return (year < 1 ? 0 : 1); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } - private long getEpochMonth() { - return ((year - 1970) * 12L) + (month - 1); + private long getProlepticMonth() { + return (year * 12L + month - 1); } //----------------------------------------------------------------------- @@ -589,8 +594,8 @@ *

    • {@code MONTH_OF_YEAR} - * Returns a {@code YearMonth} with the specified month-of-year. * The year will be unchanged. - *
    • {@code EPOCH_MONTH} - - * Returns a {@code YearMonth} with the specified epoch-month. + *
    • {@code PROLEPTIC_MONTH} - + * Returns a {@code YearMonth} with the specified proleptic-month. * This completely replaces the year and month of this object. *
    • {@code YEAR_OF_ERA} - * Returns a {@code YearMonth} with the specified year-of-era @@ -606,7 +611,7 @@ * In all cases, if the new value is outside the valid range of values for the field * then a {@code DateTimeException} will be thrown. *

      - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

      * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -619,6 +624,7 @@ * @param newValue the new value of the field in the result * @return a {@code YearMonth} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -628,12 +634,12 @@ f.checkValidValue(newValue); switch (f) { case MONTH_OF_YEAR: return withMonth((int) newValue); - case EPOCH_MONTH: return plusMonths(newValue - getLong(EPOCH_MONTH)); + case PROLEPTIC_MONTH: return plusMonths(newValue - getProlepticMonth()); case YEAR_OF_ERA: return withYear((int) (year < 1 ? 1 - newValue : newValue)); case YEAR: return withYear((int) newValue); case ERA: return (getLong(ERA) == newValue ? this : withYear(1 - year)); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.adjustInto(this, newValue); } @@ -728,7 +734,7 @@ * is unchanged. *

    *

    - * All other {@code ChronoUnit} instances will throw a {@code DateTimeException}. + * All other {@code ChronoUnit} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoUnit}, then the result of this method * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} @@ -741,6 +747,7 @@ * @param unit the unit of the amount to add, not null * @return a {@code YearMonth} based on this year-month with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -754,7 +761,7 @@ case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.addTo(this, amountToAdd); } @@ -838,6 +845,7 @@ * @param unit the unit of the amount to subtract, not null * @return a {@code YearMonth} based on this year-month with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -893,9 +901,9 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.chronology()) { + if (query == TemporalQuery.chronology()) { return (R) IsoChronology.INSTANCE; - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) MONTHS; } return Temporal.super.query(query); @@ -908,7 +916,7 @@ * with the year and month changed to be the same as this. *

    * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} - * passing {@link ChronoField#EPOCH_MONTH} as the field. + * passing {@link ChronoField#PROLEPTIC_MONTH} as the field. * If the specified temporal object does not use the ISO calendar system then * a {@code DateTimeException} is thrown. *

    @@ -932,7 +940,7 @@ if (Chronology.from(temporal).equals(IsoChronology.INSTANCE) == false) { throw new DateTimeException("Adjustment only supported on ISO date-time"); } - return temporal.with(EPOCH_MONTH, getEpochMonth()); + return temporal.with(PROLEPTIC_MONTH, getProlepticMonth()); } /** @@ -977,6 +985,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this year-month and the end year-month * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -987,7 +996,7 @@ } YearMonth end = (YearMonth) endYearMonth; if (unit instanceof ChronoUnit) { - long monthsUntil = end.getEpochMonth() - getEpochMonth(); // no overflow + long monthsUntil = end.getProlepticMonth() - getProlepticMonth(); // no overflow switch ((ChronoUnit) unit) { case MONTHS: return monthsUntil; case YEARS: return monthsUntil / 12; @@ -996,11 +1005,25 @@ case MILLENNIA: return monthsUntil / 12000; case ERAS: return end.getLong(ERA) - getLong(ERA); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.between(this, endYearMonth); } + /** + * Formats this year-month using the specified formatter. + *

    + * This year-month will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted year-month string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Combines this year-month with a day-of-month to create a {@code LocalDate}. @@ -1115,7 +1138,7 @@ /** * Outputs this year-month as a {@code String}, such as {@code 2007-12}. *

    - * The output will be in the format {@code yyyy-MM}: + * The output will be in the format {@code uuuu-MM}: * * @return a string representation of this year-month, not null */ @@ -1137,21 +1160,6 @@ .toString(); } - /** - * Outputs this year-month as a {@code String} using the formatter. - *

    - * This year-month will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted year-month string, not null - * @throws DateTimeException if an error occurs during printing - */ - public String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/ZoneId.java --- a/src/share/classes/java/time/ZoneId.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/ZoneId.java Sat Apr 13 21:51:36 2013 +0100 @@ -66,10 +66,10 @@ import java.io.Serializable; import java.time.format.DateTimeFormatterBuilder; import java.time.format.TextStyle; -import java.time.temporal.Queries; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.zone.ZoneRules; import java.time.zone.ZoneRulesException; import java.time.zone.ZoneRulesProvider; @@ -78,6 +78,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.TimeZone; /** @@ -93,6 +94,8 @@ * the offset from UTC/Greenwich apply *

    * Most fixed offsets are represented by {@link ZoneOffset}. + * Calling {@link #normalized()} on any {@code ZoneId} will ensure that a + * fixed offset ID will be represented as a {@code ZoneOffset}. *

    * The actual rules, describing when and how the offset changes, are defined by {@link ZoneRules}. * This class is simply an ID used to obtain the underlying rules. @@ -103,28 +106,29 @@ * the ID, whereas serializing the rules sends the entire data set. * Similarly, a comparison of two IDs only examines the ID, whereas * a comparison of two rules examines the entire data set. - *

    - * The code supports loading a {@code ZoneId} on a JVM which does not have available rules - * for that ID. This allows the date-time object, such as {@link ZonedDateTime}, - * to still be queried. * *

    Time-zone IDs

    * The ID is unique within the system. - * The formats for offset and region IDs differ. + * There are three types of ID. *

    - * An ID is parsed as an offset ID if it starts with 'UTC', 'GMT', 'UT' '+' or '-', or - * is a single letter. For example, 'Z', '+02:00', '-05:00', 'UTC+05', 'GMT-6' and - * 'UT+01:00' are all valid offset IDs. - * Note that some IDs, such as 'D' or '+ABC' meet the criteria to be parsed as offset IDs, - * but have an invalid offset. + * The simplest type of ID is that from {@code ZoneOffset}. + * This consists of 'Z' and IDs starting with '+' or '-'. *

    - * All other IDs are considered to be region IDs. + * The next type of ID are offset-style IDs with some form of prefix, + * such as 'GMT+2' or 'UTC+01:00'. + * The recognised prefixes are 'UTC', 'GMT' and 'UT'. + * The offset is the suffix and will be normalized during creation. + * These IDs can be normalized to a {@code ZoneOffset} using {@code normalized()}. *

    - * Region IDs are defined by configuration, which can be thought of as a {@code Map} - * from region ID to {@code ZoneRules}, see {@link ZoneRulesProvider}. + * The third type of ID are region-based IDs. A region-based ID must be of + * two or more characters, and not start with 'UTC', 'GMT', 'UT' '+' or '-'. + * Region-based IDs are defined by configuration, see {@link ZoneRulesProvider}. + * The configuration focuses on providing the lookup from the ID to the + * underlying {@code ZoneRules}. *

    - * Time-zones are defined by governments and change frequently. There are a number of - * organizations, known here as groups, that monitor time-zone changes and collate them. + * Time-zone rules are defined by governments and change frequently. + * There are a number of organizations, known here as groups, that monitor + * time-zone changes and collate them. * The default group is the IANA Time Zone Database (TZDB). * Other organizations include IATA (the airline industry body) and Microsoft. *

    @@ -139,6 +143,20 @@ * The recommended format for region IDs from groups other than TZDB is 'group~region'. * Thus if IATA data were defined, Utrecht airport would be 'IATA~UTC'. * + *

    Serialization

    + * This class can be serialized and stores the string zone ID in the external form. + * The {@code ZoneOffset} subclass uses a dedicated format that only stores the + * offset from UTC/Greenwich. + *

    + * A {@code ZoneId} can be deserialized in a Java Runtime where the ID is unknown. + * For example, if a server-side Java Runtime has been updated with a new zone ID, but + * the client-side Java Runtime has not been updated. In this case, the {@code ZoneId} + * object will exist, and can be queried using {@code getId}, {@code equals}, + * {@code hashCode}, {@code toString}, {@code getDisplayName} and {@code normalized}. + * However, any call to {@code getRules} will fail with {@code ZoneRulesException}. + * This approach is designed to allow a {@link ZonedDateTime} to be loaded and + * queried, but not modified, on a Java Runtime with incomplete time-zone information. + * *

    Specification for implementors

    * This abstract class has two implementations, both of which are immutable and thread-safe. * One implementation models region-based IDs, the other is {@code ZoneOffset} modelling @@ -149,7 +167,15 @@ public abstract class ZoneId implements Serializable { /** - * A map of zone overrides to enable the older US time-zone names to be used. + * A map of zone overrides to enable the older short time-zone names to be used. + *

    + * Use of short zone IDs has been deprecated in {@code java.util.TimeZone}. + * This map allows the IDs to continue to be used via the + * {@link #of(String, Map)} factory method. + *

    + * This map contains an older mapping of the IDs, where 'EST', 'MST' and 'HST' + * map to IDs which include daylight savings. + * This is in line with versions of TZDB before 2005r. *

    * This maps as follows: *

      @@ -184,9 +210,17 @@ *

    * The map is unmodifiable. */ - public static final Map OLD_IDS_PRE_2005; + public static final Map OLD_SHORT_IDS; /** - * A map of zone overrides to enable the older US time-zone names to be used. + * A map of zone overrides to enable the short time-zone names to be used. + *

    + * Use of short zone IDs has been deprecated in {@code java.util.TimeZone}. + * This map allows the IDs to continue to be used via the + * {@link #of(String, Map)} factory method. + *

    + * This map contains a newer mapping of the IDs, where 'EST', 'MST' and 'HST' + * map to IDs which do not include daylight savings + * This is in line with TZDB 2005r and later. *

    * This maps as follows: *

      @@ -221,7 +255,7 @@ *

    * The map is unmodifiable. */ - public static final Map OLD_IDS_POST_2005; + public static final Map SHORT_IDS; static { Map base = new HashMap<>(); base.put("ACT", "Australia/Darwin"); @@ -253,12 +287,12 @@ pre.put("EST", "America/New_York"); pre.put("MST", "America/Denver"); pre.put("HST", "Pacific/Honolulu"); - OLD_IDS_PRE_2005 = Collections.unmodifiableMap(pre); + OLD_SHORT_IDS = Collections.unmodifiableMap(pre); Map post = new HashMap<>(base); post.put("EST", "-05:00"); post.put("MST", "-07:00"); post.put("HST", "-10:00"); - OLD_IDS_POST_2005 = Collections.unmodifiableMap(post); + SHORT_IDS = Collections.unmodifiableMap(post); } /** * Serialization version. @@ -278,7 +312,23 @@ * @throws ZoneRulesException if the converted zone region ID cannot be found */ public static ZoneId systemDefault() { - return ZoneId.of(TimeZone.getDefault().getID(), OLD_IDS_POST_2005); + return ZoneId.of(TimeZone.getDefault().getID(), SHORT_IDS); + } + + /** + * Gets the set of available zone IDs. + *

    + * This set includes the string form of all available region-based IDs. + * Offset-based zone IDs are not included in the returned set. + * The ID can be passed to {@link #of(String)} to create a {@code ZoneId}. + *

    + * The set of zone IDs can increase over time, although in a typical application + * the set of IDs is fixed. Each call to this method is thread-safe. + * + * @return a modifiable copy of the set of zone IDs, not null + */ + public static Set getAvailableZoneIds() { + return ZoneRulesProvider.getAvailableZoneIds(); } //----------------------------------------------------------------------- @@ -310,31 +360,36 @@ * Obtains an instance of {@code ZoneId} from an ID ensuring that the * ID is valid and available for use. *

    - * This method parses the ID, applies any appropriate normalization, and validates it - * against the known set of IDs for which rules are available. - *

    - * An ID is parsed as though it is an offset ID if it starts with 'UTC', 'GMT', 'UT', '+' - * or '-', or if it has less then two letters. - * The offset of {@linkplain ZoneOffset#UTC zero} may be represented in multiple ways, - * including 'Z', 'UTC', 'GMT', 'UT', 'UTC0', 'GMT0', 'UT0', '+00:00', '-00:00' and 'UTC+00:00'. + * This method parses the ID producing a {@code ZoneId} or {@code ZoneOffset}. + * A {@code ZoneOffset} is returned if the ID is 'Z', or starts with '+' or '-'. + * The result will always be a valid ID for which {@link ZoneRules} can be obtained. *

    - * Six forms of ID are recognized: - *

      - *
    • Z - an offset of zero, which is {@code ZoneOffset.UTC} - *
    • {offset} - a {@code ZoneOffset} ID, such as '+02:00' - *
    • {utcPrefix} - a {@code ZoneOffset} ID equal to 'Z' - *
    • {utcPrefix}0 - a {@code ZoneOffset} ID equal to 'Z' - *
    • {utcPrefix}{offset} - a {@code ZoneOffset} ID equal to '{offset}' - *
    • {regionID} - full region ID, loaded from configuration - *

    - * The {offset} is a valid format for {@link ZoneOffset#of(String)}, excluding 'Z'. - * The {utcPrefix} is 'UTC', 'GMT' or 'UT'. - * Region IDs must match the regular expression [A-Za-z][A-Za-z0-9~/._+-]+. - *

    - * The detailed format of the region ID depends on the group supplying the data. - * The default set of data is supplied by the IANA Time Zone Database (TZDB) - * This has region IDs of the form '{area}/{city}', such as 'Europe/Paris' or 'America/New_York'. - * This is compatible with most IDs from {@link java.util.TimeZone}. + * Parsing matches the zone ID step by step as follows. + *

      + *
    • If the zone ID equals 'Z', the result is {@code ZoneOffset.UTC}. + *
    • If the zone ID consists of a single letter, the zone ID is invalid + * and {@code DateTimeException} is thrown. + *
    • If the zone ID starts with '+' or '-', the ID is parsed as a + * {@code ZoneOffset} using {@link ZoneOffset#of(String)}. + *
    • If the zone ID equals 'GMT', 'UTC' or 'UT' then the result is a {@code ZoneId} + * with the same ID and rules equivalent to {@code ZoneOffset.UTC}. + *
    • If the zone ID starts with 'UTC+', 'UTC-', 'GMT+', 'GMT-', 'UT+' or 'UT-' + * then the ID is a prefixed offset-based ID. The ID is split in two, with + * a two or three letter prefix and a suffix starting with the sign. + * The suffix is parsed as a {@link ZoneOffset#of(String) ZoneOffset}. + * The result will be a {@code ZoneId} with the specified UTC/GMT/UT prefix + * and the normalized offset ID as per {@link ZoneOffset#getId()}. + * The rules of the returned {@code ZoneId} will be equivalent to the + * parsed {@code ZoneOffset}. + *
    • All other IDs are parsed as region-based zone IDs. Region IDs must + * match the regular expression [A-Za-z][A-Za-z0-9~/._+-]+ + * otherwise a {@code DateTimeException} is thrown. If the zone ID is not + * in the configured set of IDs, {@code ZoneRulesException} is thrown. + * The detailed format of the region ID depends on the group supplying the data. + * The default set of data is supplied by the IANA Time Zone Database (TZDB). + * This has region IDs of the form '{area}/{city}', such as 'Europe/Paris' or 'America/New_York'. + * This is compatible with most IDs from {@link java.util.TimeZone}. + *
    * * @param zoneId the time-zone ID, not null * @return the zone ID, not null @@ -342,15 +397,29 @@ * @throws ZoneRulesException if the zone ID is a region ID that cannot be found */ public static ZoneId of(String zoneId) { + return of(zoneId, true); + } + + /** + * Parses the ID, taking a flag to indicate whether {@code ZoneRulesException} + * should be thrown or not, used in deserialization. + * + * @param zoneId the time-zone ID, not null + * @param checkAvailable whether to check if the zone ID is available + * @return the zone ID, not null + * @throws DateTimeException if the ID format is invalid + * @throws ZoneRulesException if checking availability and the ID cannot be found + */ + static ZoneId of(String zoneId, boolean checkAvailable) { Objects.requireNonNull(zoneId, "zoneId"); if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) { return ZoneOffset.of(zoneId); } else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) { - return ofWithPrefix(zoneId, 3); + return ofWithPrefix(zoneId, 3, checkAvailable); } else if (zoneId.startsWith("UT")) { - return ofWithPrefix(zoneId, 2); + return ofWithPrefix(zoneId, 2, checkAvailable); } - return ZoneRegion.ofId(zoneId, true); + return ZoneRegion.ofId(zoneId, checkAvailable); } /** @@ -359,22 +428,25 @@ * @param zoneId the time-zone ID, not null * @param prefixLength the length of the prefix, 2 or 3 * @return the zone ID, not null - * @return the zone ID, not null * @throws DateTimeException if the zone ID has an invalid format */ - private static ZoneId ofWithPrefix(String zoneId, int prefixLength) { - if (zoneId.length() == prefixLength || - (zoneId.length() == prefixLength + 1 && zoneId.charAt(prefixLength) == '0')) { - return ZoneOffset.UTC; + private static ZoneId ofWithPrefix(String zoneId, int prefixLength, boolean checkAvailable) { + String prefix = zoneId.substring(0, prefixLength); + if (zoneId.length() == prefixLength) { + return ZoneRegion.ofPrefixedOffset(prefix, ZoneOffset.UTC); + } + if (zoneId.charAt(prefixLength) != '+' && zoneId.charAt(prefixLength) != '-') { + return ZoneRegion.ofId(zoneId, checkAvailable); // drop through to ZoneRulesProvider } - if (zoneId.charAt(prefixLength) == '+' || zoneId.charAt(prefixLength) == '-') { - try { - return ZoneOffset.of(zoneId.substring(prefixLength)); - } catch (DateTimeException ex) { - throw new DateTimeException("Invalid ID for offset-based ZoneId: " + zoneId, ex); + try { + ZoneOffset offset = ZoneOffset.of(zoneId.substring(prefixLength)); + if (offset == ZoneOffset.UTC) { + return ZoneRegion.ofPrefixedOffset(prefix, offset); } + return ZoneRegion.ofPrefixedOffset(prefix + offset.toString(), offset); + } catch (DateTimeException ex) { + throw new DateTimeException("Invalid ID for offset-based ZoneId: " + zoneId, ex); } - throw new DateTimeException("Invalid ID for offset-based ZoneId: " + zoneId); } //----------------------------------------------------------------------- @@ -389,7 +461,7 @@ * This factory converts the arbitrary temporal object to an instance of {@code ZoneId}. *

    * The conversion will try to obtain the zone in a way that favours region-based - * zones over offset-based zones using {@link Queries#zone()}. + * zones over offset-based zones using {@link TemporalQuery#zone()}. *

    * This method matches the signature of the functional interface {@link TemporalQuery} * allowing it to be used in queries via method reference, {@code ZoneId::from}. @@ -399,7 +471,7 @@ * @throws DateTimeException if unable to convert to a {@code ZoneId} */ public static ZoneId from(TemporalAccessor temporal) { - ZoneId obj = temporal.query(Queries.zone()); + ZoneId obj = temporal.query(TemporalQuery.zone()); if (obj == null) { throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " + temporal.getClass()); } @@ -429,29 +501,6 @@ //----------------------------------------------------------------------- /** - * Gets the time-zone rules for this ID allowing calculations to be performed. - *

    - * The rules provide the functionality associated with a time-zone, - * such as finding the offset for a given instant or local date-time. - *

    - * A time-zone can be invalid if it is deserialized in a JVM which does not - * have the same rules loaded as the JVM that stored it. In this case, calling - * this method will throw an exception. - *

    - * The rules are supplied by {@link ZoneRulesProvider}. An advanced provider may - * support dynamic updates to the rules without restarting the JVM. - * If so, then the result of this method may change over time. - * Each individual call will be still remain thread-safe. - *

    - * {@link ZoneOffset} will always return a set of rules where the offset never changes. - * - * @return the rules, not null - * @throws ZoneRulesException if no rules are available for this ID - */ - public abstract ZoneRules getRules(); - - //----------------------------------------------------------------------- - /** * Gets the textual representation of the zone, such as 'British Time' or * '+02:00'. *

    @@ -466,24 +515,88 @@ * @return the text value of the zone, not null */ public String getDisplayName(TextStyle style, Locale locale) { - return new DateTimeFormatterBuilder().appendZoneText(style).toFormatter(locale).format(new TemporalAccessor() { + return new DateTimeFormatterBuilder().appendZoneText(style).toFormatter(locale).format(toTemporal()); + } + + /** + * Converts this zone to a {@code TemporalAccessor}. + *

    + * A {@code ZoneId} can be fully represented as a {@code TemporalAccessor}. + * However, the interface is not implemented by this class as most of the + * methods on the interface have no meaning to {@code ZoneId}. + *

    + * The returned temporal has no supported fields, with the query method + * supporting the return of the zone using {@link TemporalQuery#zoneId()}. + * + * @return a temporal equivalent to this zone, not null + */ + private TemporalAccessor toTemporal() { + return new TemporalAccessor() { @Override public boolean isSupported(TemporalField field) { return false; } @Override public long getLong(TemporalField field) { - throw new DateTimeException("Unsupported field: " + field); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field); } @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.zoneId()) { + if (query == TemporalQuery.zoneId()) { return (R) ZoneId.this; } return TemporalAccessor.super.query(query); } - }); + }; + } + + //----------------------------------------------------------------------- + /** + * Gets the time-zone rules for this ID allowing calculations to be performed. + *

    + * The rules provide the functionality associated with a time-zone, + * such as finding the offset for a given instant or local date-time. + *

    + * A time-zone can be invalid if it is deserialized in a Java Runtime which + * does not have the same rules loaded as the Java Runtime that stored it. + * In this case, calling this method will throw a {@code ZoneRulesException}. + *

    + * The rules are supplied by {@link ZoneRulesProvider}. An advanced provider may + * support dynamic updates to the rules without restarting the Java Runtime. + * If so, then the result of this method may change over time. + * Each individual call will be still remain thread-safe. + *

    + * {@link ZoneOffset} will always return a set of rules where the offset never changes. + * + * @return the rules, not null + * @throws ZoneRulesException if no rules are available for this ID + */ + public abstract ZoneRules getRules(); + + /** + * Normalizes the time-zone ID, returning a {@code ZoneOffset} where possible. + *

    + * The returns a normalized {@code ZoneId} that can be used in place of this ID. + * The result will have {@code ZoneRules} equivalent to those returned by this object, + * however the ID returned by {@code getId()} may be different. + *

    + * The normalization checks if the rules of this {@code ZoneId} have a fixed offset. + * If they do, then the {@code ZoneOffset} equal to that offset is returned. + * Otherwise {@code this} is returned. + * + * @return the time-zone unique ID, not null + */ + public ZoneId normalized() { + try { + ZoneRules rules = getRules(); + if (rules.isFixedOffset()) { + return rules.getOffset(Instant.EPOCH); + } + } catch (ZoneRulesException ex) { + // invalid ZoneRegion is not important to this method + } + return this; } //----------------------------------------------------------------------- @@ -536,6 +649,10 @@ * out.writeByte(7); // identifies this as a ZoneId (not ZoneOffset) * out.writeUTF(zoneId); * + *

    + * When read back in, the {@code ZoneId} will be created as though using + * {@link #of(String)}, but without any exception in the case where the + * ID has a valid format, but is not in the known set of region-based IDs. * * @return the instance of {@code Ser}, not null */ diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/ZoneOffset.java --- a/src/share/classes/java/time/ZoneOffset.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/ZoneOffset.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,6 +61,7 @@ */ package java.time; +import java.time.temporal.UnsupportedTemporalTypeException; import static java.time.LocalTime.MINUTES_PER_HOUR; import static java.time.LocalTime.SECONDS_PER_HOUR; import static java.time.LocalTime.SECONDS_PER_MINUTE; @@ -73,7 +74,6 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -322,7 +322,7 @@ * A {@code TemporalAccessor} represents some form of date and time information. * This factory converts the arbitrary temporal object to an instance of {@code ZoneOffset}. *

    - * The conversion uses the {@link Queries#offset()} query, which relies + * The conversion uses the {@link TemporalQuery#offset()} query, which relies * on extracting the {@link ChronoField#OFFSET_SECONDS OFFSET_SECONDS} field. *

    * This method matches the signature of the functional interface {@link TemporalQuery} @@ -333,7 +333,7 @@ * @throws DateTimeException if unable to convert to an {@code ZoneOffset} */ public static ZoneOffset from(TemporalAccessor temporal) { - ZoneOffset offset = temporal.query(Queries.offset()); + ZoneOffset offset = temporal.query(TemporalQuery.offset()); if (offset == null) { throw new DateTimeException("Unable to obtain ZoneOffset from TemporalAccessor: " + temporal.getClass()); } @@ -534,7 +534,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -544,6 +544,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override // override for Javadoc public ValueRange range(TemporalField field) { @@ -560,7 +561,7 @@ *

    * If the field is a {@link ChronoField} then the query is implemented here. * The {@code OFFSET_SECONDS} field returns the value of the offset. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -569,7 +570,10 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc and performance @@ -577,7 +581,7 @@ if (field == OFFSET_SECONDS) { return totalSeconds; } else if (field instanceof ChronoField) { - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return range(field).checkValidIntValue(getLong(field), field); } @@ -591,7 +595,7 @@ *

    * If the field is a {@link ChronoField} then the query is implemented here. * The {@code OFFSET_SECONDS} field returns the value of the offset. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -601,6 +605,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -608,7 +613,7 @@ if (field == OFFSET_SECONDS) { return totalSeconds; } else if (field instanceof ChronoField) { - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } @@ -635,7 +640,7 @@ @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.offset() || query == Queries.zone()) { + if (query == TemporalQuery.offset() || query == TemporalQuery.zone()) { return (R) this; } return TemporalAccessor.super.query(query); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/ZoneRegion.java --- a/src/share/classes/java/time/ZoneRegion.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/ZoneRegion.java Sat Apr 13 21:51:36 2013 +0100 @@ -95,11 +95,6 @@ */ private static final long serialVersionUID = 8386373296231747096L; /** - * The regex pattern for region IDs. - */ - private static final Pattern PATTERN = Pattern.compile("[A-Za-z][A-Za-z0-9~/._+-]+"); - - /** * The time-zone ID, not null. */ private final String id; @@ -109,26 +104,6 @@ private final transient ZoneRules rules; /** - * Obtains an instance of {@code ZoneRegion} from an identifier without checking - * if the time-zone has available rules. - *

    - * This method parses the ID and applies any appropriate normalization. - * It does not validate the ID against the known set of IDs for which rules are available. - *

    - * This method is intended for advanced use cases. - * For example, consider a system that always retrieves time-zone rules from a remote server. - * Using this factory would allow a {@code ZoneRegion}, and thus a {@code ZonedDateTime}, - * to be created without loading the rules from the remote server. - * - * @param zoneId the time-zone ID, not null - * @return the zone ID, not null - * @throws DateTimeException if the ID format is invalid - */ - private static ZoneRegion ofLenient(String zoneId) { - return ofId(zoneId, false); - } - - /** * Obtains an instance of {@code ZoneId} from an identifier. * * @param zoneId the time-zone ID, not null @@ -139,12 +114,7 @@ */ static ZoneRegion ofId(String zoneId, boolean checkAvailable) { Objects.requireNonNull(zoneId, "zoneId"); - if (zoneId.length() < 2 || - zoneId.startsWith("UT") || // includes UTC - zoneId.startsWith("GMT") || - (PATTERN.matcher(zoneId).matches() == false)) { - throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); - } + checkName(zoneId); ZoneRules rules = null; try { // always attempt load for better behavior after deserialization @@ -157,6 +127,45 @@ return new ZoneRegion(zoneId, rules); } + /** + * Checks that the given string is a legal ZondId name. + * + * @param zoneId the time-zone ID, not null + * @throws DateTimeException if the ID format is invalid + */ + private static void checkName(String zoneId) { + int n = zoneId.length(); + if (n < 2) { + throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); + } + for (int i = 0; i < n; i++) { + char c = zoneId.charAt(i); + if (c >= 'a' && c <= 'z') continue; + if (c >= 'A' && c <= 'Z') continue; + if (c == '/' && i != 0) continue; + if (c >= '0' && c <= '9' && i != 0) continue; + if (c == '~' && i != 0) continue; + if (c == '.' && i != 0) continue; + if (c == '_' && i != 0) continue; + if (c == '+' && i != 0) continue; + if (c == '-' && i != 0) continue; + throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); + } + } + + /** + * Obtains an instance of {@code ZoneId} wrapping an offset. + *

    + * For example, zone IDs like 'UTC', 'GMT', 'UT' and 'UTC+01:30' will be setup here. + * + * @param zoneId the time-zone ID, not null + * @param offset the offset, not null + * @return the zone ID, not null + */ + static ZoneRegion ofPrefixedOffset(String zoneId, ZoneOffset offset) { + return new ZoneRegion(zoneId, offset.getRules()); + } + //------------------------------------------------------------------------- /** * Constructor. @@ -218,7 +227,7 @@ static ZoneId readExternal(DataInput in) throws IOException { String id = in.readUTF(); - return ofLenient(id); + return ZoneId.of(id, false); } } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/ZonedDateTime.java --- a/src/share/classes/java/time/ZonedDateTime.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/ZonedDateTime.java Sat Apr 13 21:51:36 2013 +0100 @@ -72,7 +72,6 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.time.chrono.ChronoZonedDateTime; -import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; @@ -84,6 +83,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.time.zone.ZoneOffsetTransition; import java.time.zone.ZoneRules; @@ -136,6 +136,11 @@ * used, typically "summer" time.. Two additional methods, * {@link #withEarlierOffsetAtOverlap()} and {@link #withLaterOffsetAtOverlap()}, * help manage the case of an overlap. + *

    + * In terms of design, this class should be viewed primarily as the combination + * of a {@code LocalDateTime} and a {@code ZoneId}. The {@code ZoneOffset} is + * a vital, but secondary, piece of information, used to ensure that the class + * represents an instant, especially during a daylight savings overlap. * *

    Specification for implementors

    * A {@code ZonedDateTime} holds state equivalent to three separate objects, @@ -420,6 +425,9 @@ Objects.requireNonNull(localDateTime, "localDateTime"); Objects.requireNonNull(offset, "offset"); Objects.requireNonNull(zone, "zone"); + if (zone.getRules().isValidOffset(localDateTime, offset)) { + return new ZonedDateTime(localDateTime, offset, zone); + } return create(localDateTime.toEpochSecond(offset), localDateTime.getNano(), zone); } @@ -615,17 +623,18 @@ } /** - * Resolves the offset into this zoned date-time. + * Resolves the offset into this zoned date-time for the with methods. *

    - * This will use the new offset to find the instant, which is then looked up - * using the zone ID to find the actual offset to use. + * This typically ignores the offset, unless it can be used to switch offset in a DST overlap. * * @param offset the offset, not null * @return the zoned date-time, not null */ private ZonedDateTime resolveOffset(ZoneOffset offset) { - long epSec = dateTime.toEpochSecond(offset); - return create(epSec, dateTime.getNano(), zone); + if (offset.equals(this.offset) == false && zone.getRules().isValidOffset(dateTime, offset)) { + return new ZonedDateTime(dateTime, offset, zone); + } + return this; } //----------------------------------------------------------------------- @@ -663,7 +672,7 @@ *

  • {@code ALIGNED_WEEK_OF_MONTH} *
  • {@code ALIGNED_WEEK_OF_YEAR} *
  • {@code MONTH_OF_YEAR} - *
  • {@code EPOCH_MONTH} + *
  • {@code PROLEPTIC_MONTH} *
  • {@code YEAR_OF_ERA} *
  • {@code YEAR} *
  • {@code ERA} @@ -696,7 +705,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return * appropriate range instances. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} @@ -706,6 +715,7 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ @Override public ValueRange range(TemporalField field) { @@ -729,9 +739,9 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time, except {@code NANO_OF_DAY}, {@code MICRO_OF_DAY}, - * {@code EPOCH_DAY}, {@code EPOCH_MONTH} and {@code INSTANT_SECONDS} which are too + * {@code EPOCH_DAY}, {@code PROLEPTIC_MONTH} and {@code INSTANT_SECONDS} which are too * large to fit in an {@code int} and throw a {@code DateTimeException}. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -740,15 +750,20 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc and performance public int get(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { - case INSTANT_SECONDS: throw new DateTimeException("Field too large for an int: " + field); - case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + case INSTANT_SECONDS: + throw new UnsupportedTemporalTypeException("Invalid field 'InstantSeconds' for get() method, use getLong() instead"); + case OFFSET_SECONDS: + return getOffset().getTotalSeconds(); } return dateTime.get(field); } @@ -765,7 +780,7 @@ * If the field is a {@link ChronoField} then the query is implemented here. * The {@link #isSupported(TemporalField) supported fields} will return valid * values based on this date-time. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -775,6 +790,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1115,7 +1131,7 @@ *

    * A simple adjuster might simply set the one of the fields, such as the year field. * A more complex adjuster might set the date to the last day of the month. - * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * A selection of common adjustments is provided in {@link TemporalAdjuster}. * These include finding the "last day of the month" and "next Wednesday". * Key date-time classes also implement the {@code TemporalAdjuster} interface, * such as {@link Month} and {@link java.time.MonthDay MonthDay}. @@ -1137,15 +1153,12 @@ * result = zonedDateTime.with(time); * *

    - * {@link ZoneOffset} also implements {@code TemporalAdjuster} however it is less likely - * that setting the offset will have the effect you expect. When an offset is passed in, - * the local date-time is combined with the new offset to form an {@code Instant}. - * The instant and original zone are then used to create the result. - * This algorithm means that it is quite likely that the output has a different offset - * to the specified offset. It will however work correctly when passing in the offset - * applicable for the instant of the zoned date-time, and will work correctly if passing - * one of the two valid offsets during a daylight savings overlap when the same local time - * occurs twice. + * {@link ZoneOffset} also implements {@code TemporalAdjuster} however using it + * as an argument typically has no effect. The offset of a {@code ZonedDateTime} is + * controlled primarily by the time-zone. As such, changing the offset does not generally + * make sense, because there is only one valid offset for the local date-time and zone. + * If the zoned date-time is in a daylight savings overlap, then the offset is used + * to switch between the two valid offsets. In all other cases, the offset is ignored. *

    * The result of this method is obtained by invoking the * {@link TemporalAdjuster#adjustInto(Temporal)} method on the @@ -1167,6 +1180,9 @@ return resolveLocal(LocalDateTime.of(dateTime.toLocalDate(), (LocalTime) adjuster)); } else if (adjuster instanceof LocalDateTime) { return resolveLocal((LocalDateTime) adjuster); + } else if (adjuster instanceof OffsetDateTime) { + OffsetDateTime odt = (OffsetDateTime) adjuster; + return ofLocal(odt.toLocalDateTime(), zone, odt.getOffset()); } else if (adjuster instanceof Instant) { Instant instant = (Instant) adjuster; return create(instant.getEpochSecond(), instant.getNano(), zone); @@ -1197,15 +1213,13 @@ * The result will have an offset derived from the new instant and original zone. * If the new instant value is outside the valid range then a {@code DateTimeException} will be thrown. *

    - * The {@code OFFSET_SECONDS} field will return a date-time calculated using the specified offset. - * The local date-time is combined with the new offset to form an {@code Instant}. - * The instant and original zone are then used to create the result. - * This algorithm means that it is quite likely that the output has a different offset - * to the specified offset. It will however work correctly when passing in the offset - * applicable for the instant of the zoned date-time, and will work correctly if passing - * one of the two valid offsets during a daylight savings overlap when the same local time - * occurs twice. If the new offset value is outside the valid range then a - * {@code DateTimeException} will be thrown. + * The {@code OFFSET_SECONDS} field will typically be ignored. + * The offset of a {@code ZonedDateTime} is controlled primarily by the time-zone. + * As such, changing the offset does not generally make sense, because there is only + * one valid offset for the local date-time and zone. + * If the zoned date-time is in a daylight savings overlap, then the offset is used + * to switch between the two valid offsets. In all other cases, the offset is ignored. + * If the new offset value is outside the valid range then a {@code DateTimeException} will be thrown. *

    * The other {@link #isSupported(TemporalField) supported fields} will behave as per * the matching method on {@link LocalDateTime#with(TemporalField, long) LocalDateTime}. @@ -1214,7 +1228,7 @@ * then the offset will be retained if possible, otherwise the earlier offset will be used. * If in a gap, the local date-time will be adjusted forward by the length of the gap. *

    - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -1227,6 +1241,7 @@ * @param newValue the new value of the field in the result * @return a {@code ZonedDateTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1234,11 +1249,11 @@ if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; switch (f) { - case INSTANT_SECONDS: return create(newValue, getNano(), zone); - case OFFSET_SECONDS: { + case INSTANT_SECONDS: + return create(newValue, getNano(), zone); + case OFFSET_SECONDS: ZoneOffset offset = ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue)); return resolveOffset(offset); - } } return resolveLocal(dateTime.with(field, newValue)); } @@ -1453,6 +1468,7 @@ * @param unit the unit to truncate to, not null * @return a {@code ZonedDateTime} based on this date-time with the time truncated, not null * @throws DateTimeException if unable to truncate + * @throws UnsupportedTemporalTypeException if the unit is not supported */ public ZonedDateTime truncatedTo(TemporalUnit unit) { return resolveLocal(dateTime.truncatedTo(unit)); @@ -1518,6 +1534,7 @@ * @param unit the unit of the amount to add, not null * @return a {@code ZonedDateTime} based on this date-time with the specified amount added, not null * @throws DateTimeException if the addition cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -1761,6 +1778,7 @@ * @param unit the unit of the amount to subtract, not null * @return a {@code ZonedDateTime} based on this date-time with the specified amount subtracted, not null * @throws DateTimeException if the subtraction cannot be made + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -2025,6 +2043,7 @@ * @param unit the unit to measure the period in, not null * @return the amount of the period between this date-time and the end date-time * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override @@ -2046,6 +2065,21 @@ return unit.between(this, endDateTime); } + /** + * Formats this date-time using the specified formatter. + *

    + * This date-time will be passed to the formatter to produce a string. + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + @Override // override for Javadoc and performance + public String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Converts this date-time to an {@code OffsetDateTime}. @@ -2113,21 +2147,6 @@ return str; } - /** - * Outputs this date-time as a {@code String} using the formatter. - *

    - * This date will be passed to the formatter - * {@link DateTimeFormatter#format(TemporalAccessor) format method}. - * - * @param formatter the formatter to use, not null - * @return the formatted date-time string, not null - * @throws DateTimeException if an error occurs during printing - */ - @Override // override for Javadoc - public String toString(DateTimeFormatter formatter) { - return ChronoZonedDateTime.super.toString(formatter); - } - //----------------------------------------------------------------------- /** * Writes the object using a diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ChronoDateImpl.java --- a/src/share/classes/java/time/chrono/ChronoDateImpl.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ChronoDateImpl.java Sat Apr 13 21:51:36 2013 +0100 @@ -59,19 +59,18 @@ import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.ERA; import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; import static java.time.temporal.ChronoField.YEAR_OF_ERA; import java.io.Serializable; import java.time.DateTimeException; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.chrono.Chronology; -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.ChronoLocalDateTime; import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.time.temporal.ValueRange; +import java.util.Objects; /** * A date expressed in terms of a standard year-month-day calendar system. @@ -97,12 +96,12 @@ * // Enumerate the list of available calendars and print today for each * Set<Chronology> chronos = Chronology.getAvailableChronologies(); * for (Chronology chrono : chronos) { - * ChronoLocalDate date = chrono.dateNow(); + * ChronoLocalDate<?> date = chrono.dateNow(); * System.out.printf(" %20s: %s%n", chrono.getID(), date.toString()); * } * * // Print the Hijrah date and calendar - * ChronoLocalDate date = Chronology.of("Hijrah").dateNow(); + * ChronoLocalDate<?> date = Chronology.of("Hijrah").dateNow(); * int day = date.get(ChronoField.DAY_OF_MONTH); * int dow = date.get(ChronoField.DAY_OF_WEEK); * int month = date.get(ChronoField.MONTH_OF_YEAR); @@ -111,10 +110,10 @@ * dow, day, month, year); * // Print today's date and the last day of the year - * ChronoLocalDate now1 = Chronology.of("Hijrah").dateNow(); - * ChronoLocalDate first = now1.with(ChronoField.DAY_OF_MONTH, 1) + * ChronoLocalDate<?> now1 = Chronology.of("Hijrah").dateNow(); + * ChronoLocalDate<?> first = now1.with(ChronoField.DAY_OF_MONTH, 1) * .with(ChronoField.MONTH_OF_YEAR, 1); - * ChronoLocalDate last = first.plus(1, ChronoUnit.YEARS) + * ChronoLocalDate<?> last = first.plus(1, ChronoUnit.YEARS) * .minus(1, ChronoUnit.DAYS); * System.out.printf(" Today is %s: start: %s; end: %s%n", last.getChronology().getID(), * first, last); @@ -168,7 +167,7 @@ case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); } - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return ChronoLocalDate.super.plus(amountToAdd, unit); } @@ -323,6 +322,8 @@ */ @Override public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + Objects.requireNonNull(endDateTime, "endDateTime"); + Objects.requireNonNull(unit, "unit"); if (endDateTime instanceof ChronoLocalDate == false) { throw new DateTimeException("Unable to calculate period between objects of two different types"); } @@ -331,11 +332,35 @@ throw new DateTimeException("Unable to calculate period between two different chronologies"); } if (unit instanceof ChronoUnit) { - return LocalDate.from(this).periodUntil(end, unit); // TODO: this is wrong + switch ((ChronoUnit) unit) { + case DAYS: return daysUntil(end); + case WEEKS: return daysUntil(end) / 7; + case MONTHS: return monthsUntil(end); + case YEARS: return monthsUntil(end) / 12; + case DECADES: return monthsUntil(end) / 120; + case CENTURIES: return monthsUntil(end) / 1200; + case MILLENNIA: return monthsUntil(end) / 12000; + case ERAS: return end.getLong(ERA) - getLong(ERA); + } + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return unit.between(this, endDateTime); } + private long daysUntil(ChronoLocalDate end) { + return end.toEpochDay() - toEpochDay(); // no overflow + } + + private long monthsUntil(ChronoLocalDate end) { + ValueRange range = getChronology().range(MONTH_OF_YEAR); + if (range.getMaximum() != 12) { + throw new IllegalStateException("ChronoDateImpl only supports Chronologies with 12 months per year"); + } + long packed1 = getLong(PROLEPTIC_MONTH) * 32L + get(DAY_OF_MONTH); // no overflow + long packed2 = end.getLong(PROLEPTIC_MONTH) * 32L + end.get(DAY_OF_MONTH); // no overflow + return (packed2 - packed1) / 32; + } + @Override public boolean equals(Object obj) { if (this == obj) { diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ChronoLocalDate.java --- a/src/share/classes/java/time/chrono/ChronoLocalDate.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ChronoLocalDate.java Sat Apr 13 21:51:36 2013 +0100 @@ -73,7 +73,6 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -81,6 +80,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.util.Comparator; import java.util.Objects; @@ -249,25 +249,55 @@ extends Temporal, TemporalAdjuster, Comparable> { /** - * Comparator for two {@code ChronoLocalDate}s ignoring the chronology. + * Gets a comparator that compares {@code ChronoLocalDate} in + * time-line order ignoring the chronology. *

    * This comparator differs from the comparison in {@link #compareTo} in that it * only compares the underlying date and not the chronology. * This allows dates in different calendar systems to be compared based - * on the time-line position. - * This is equivalent to using {@code Long.compare(date1.toEpochDay(), date2.toEpochDay())}. + * on the position of the date on the local time-line. + * The underlying comparison is equivalent to comparing the epoch-day. * * @see #isAfter * @see #isBefore * @see #isEqual */ - public static final Comparator> DATE_COMPARATOR = - new Comparator>() { - @Override - public int compare(ChronoLocalDate date1, ChronoLocalDate date2) { - return Long.compare(date1.toEpochDay(), date2.toEpochDay()); + static Comparator> timeLineOrder() { + return Chronology.DATE_ORDER; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ChronoLocalDate} from a temporal object. + *

    + * This obtains a local date based on the specified temporal. + * A {@code TemporalAccessor} represents an arbitrary set of date and time information, + * which this factory converts to an instance of {@code ChronoLocalDate}. + *

    + * The conversion extracts and combines the chronology and the date + * from the temporal object. The behavior is equivalent to using + * {@link Chronology#date(TemporalAccessor)} with the extracted chronology. + * Implementations are permitted to perform optimizations such as accessing + * those fields that are equivalent to the relevant objects. + *

    + * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code ChronoLocalDate::from}. + * + * @param temporal the temporal object to convert, not null + * @return the date, not null + * @throws DateTimeException if unable to convert to a {@code ChronoLocalDate} + * @see Chronology#date(TemporalAccessor) + */ + static ChronoLocalDate from(TemporalAccessor temporal) { + if (temporal instanceof ChronoLocalDate) { + return (ChronoLocalDate) temporal; } - }; + Chronology chrono = temporal.query(TemporalQuery.chronology()); + if (chrono == null) { + throw new DateTimeException("Unable to obtain ChronoLocalDate from TemporalAccessor: " + temporal.getClass()); + } + return chrono.date(temporal); + } //----------------------------------------------------------------------- /** @@ -295,7 +325,7 @@ * * @return the chronology specific era constant applicable at this date, not null */ - public default Era getEra() { + default Era getEra() { return getChronology().eraOf(get(ERA)); } @@ -310,7 +340,7 @@ * * @return true if this date is in a leap year, false otherwise */ - public default boolean isLeapYear() { + default boolean isLeapYear() { return getChronology().isLeapYear(getLong(YEAR)); } @@ -332,14 +362,14 @@ * * @return the length of the year in days */ - public default int lengthOfYear() { + default int lengthOfYear() { return (isLeapYear() ? 366 : 365); } @Override - public default boolean isSupported(TemporalField field) { + default boolean isSupported(TemporalField field) { if (field instanceof ChronoField) { - return ((ChronoField) field).isDateField(); + return field.isDateBased(); } return field != null && field.isSupportedBy(this); } @@ -352,19 +382,20 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default D with(TemporalAdjuster adjuster) { + default D with(TemporalAdjuster adjuster) { return (D) getChronology().ensureChronoLocalDate(Temporal.super.with(adjuster)); } /** * {@inheritDoc} * @throws DateTimeException {@inheritDoc} + * @throws UnsupportedTemporalTypeException {@inheritDoc} * @throws ArithmeticException {@inheritDoc} */ @Override - public default D with(TemporalField field, long newValue) { + default D with(TemporalField field, long newValue) { if (field instanceof ChronoField) { - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return (D) getChronology().ensureChronoLocalDate(field.adjustInto(this, newValue)); } @@ -375,7 +406,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default D plus(TemporalAmount amount) { + default D plus(TemporalAmount amount) { return (D) getChronology().ensureChronoLocalDate(Temporal.super.plus(amount)); } @@ -385,9 +416,9 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default D plus(long amountToAdd, TemporalUnit unit) { + default D plus(long amountToAdd, TemporalUnit unit) { if (unit instanceof ChronoUnit) { - throw new DateTimeException("Unsupported unit: " + unit.getName()); + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit.getName()); } return (D) getChronology().ensureChronoLocalDate(unit.addTo(this, amountToAdd)); } @@ -398,17 +429,18 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default D minus(TemporalAmount amount) { + default D minus(TemporalAmount amount) { return (D) getChronology().ensureChronoLocalDate(Temporal.super.minus(amount)); } /** * {@inheritDoc} * @throws DateTimeException {@inheritDoc} + * @throws UnsupportedTemporalTypeException {@inheritDoc} * @throws ArithmeticException {@inheritDoc} */ @Override - public default D minus(long amountToSubtract, TemporalUnit unit) { + default D minus(long amountToSubtract, TemporalUnit unit) { return (D) getChronology().ensureChronoLocalDate(Temporal.super.minus(amountToSubtract, unit)); } @@ -433,14 +465,14 @@ */ @SuppressWarnings("unchecked") @Override - public default R query(TemporalQuery query) { - if (query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + default R query(TemporalQuery query) { + if (query == TemporalQuery.zoneId() || query == TemporalQuery.zone() || query == TemporalQuery.offset()) { return null; - } else if (query == Queries.localTime()) { + } else if (query == TemporalQuery.localTime()) { return null; - } else if (query == Queries.chronology()) { + } else if (query == TemporalQuery.chronology()) { return (R) getChronology(); - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) DAYS; } // inline TemporalAccessor.super.query(query) as an optimization @@ -473,7 +505,7 @@ * @throws ArithmeticException if numeric overflow occurs */ @Override - public default Temporal adjustInto(Temporal temporal) { + default Temporal adjustInto(Temporal temporal) { return temporal.with(EPOCH_DAY, toEpochDay()); } @@ -522,7 +554,7 @@ * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc - public abstract long periodUntil(Temporal endDate, TemporalUnit unit); + long periodUntil(Temporal endDate, TemporalUnit unit); /** * Calculates the period between this date and another date as a {@code Period}. @@ -530,13 +562,11 @@ * This calculates the period between two dates in terms of years, months and days. * The start and end points are {@code this} and the specified date. * The result will be negative if the end is before the start. + * The negative sign will be the same in each of year, month and day. *

    - * The calculation is performed using the the chronology of this date. + * The calculation is performed using the chronology of this date. * If necessary, the input date will be converted to match. *

    - * The result of this method can be a negative period if the end is before the start. - * The negative sign will be the same in each of year, month and day. - *

    * This instance is immutable and unaffected by this method call. * * @param endDate the end date, exclusive, which may be in any chronology, not null @@ -544,7 +574,26 @@ * @throws DateTimeException if the period cannot be calculated * @throws ArithmeticException if numeric overflow occurs */ - public abstract Period periodUntil(ChronoLocalDate endDate); + Period periodUntil(ChronoLocalDate endDate); + + /** + * Formats this date using the specified formatter. + *

    + * This date will be passed to the formatter to produce a string. + *

    + * The default implementation must behave as follows: + *

    +     *  return formatter.format(this);
    +     * 
    + * + * @param formatter the formatter to use, not null + * @return the formatted date string, not null + * @throws DateTimeException if an error occurs during printing + */ + default String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } //----------------------------------------------------------------------- /** @@ -556,7 +605,7 @@ * @param localTime the local time to use, not null * @return the local date-time formed from this date and the specified time, not null */ - public default ChronoLocalDateTime atTime(LocalTime localTime) { + default ChronoLocalDateTime atTime(LocalTime localTime) { return (ChronoLocalDateTime)ChronoLocalDateTimeImpl.of(this, localTime); } @@ -572,7 +621,7 @@ * * @return the Epoch Day equivalent to this date */ - public default long toEpochDay() { + default long toEpochDay() { return getLong(EPOCH_DAY); } @@ -606,7 +655,7 @@ * @return the comparator value, negative if less, positive if greater */ @Override - public default int compareTo(ChronoLocalDate other) { + default int compareTo(ChronoLocalDate other) { int cmp = Long.compare(toEpochDay(), other.toEpochDay()); if (cmp == 0) { cmp = getChronology().compareTo(other.getChronology()); @@ -628,7 +677,7 @@ * @param other the other date to compare to, not null * @return true if this is after the specified date */ - public default boolean isAfter(ChronoLocalDate other) { + default boolean isAfter(ChronoLocalDate other) { return this.toEpochDay() > other.toEpochDay(); } @@ -646,7 +695,7 @@ * @param other the other date to compare to, not null * @return true if this is before the specified date */ - public default boolean isBefore(ChronoLocalDate other) { + default boolean isBefore(ChronoLocalDate other) { return this.toEpochDay() < other.toEpochDay(); } @@ -664,7 +713,7 @@ * @param other the other date to compare to, not null * @return true if the underlying date is equal to the specified date */ - public default boolean isEqual(ChronoLocalDate other) { + default boolean isEqual(ChronoLocalDate other) { return this.toEpochDay() == other.toEpochDay(); } @@ -695,28 +744,11 @@ /** * Outputs this date as a {@code String}. *

    - * The output will include the full local date and the chronology ID. + * The output will include the full local date. * * @return the formatted date, not null */ @Override String toString(); - /** - * Outputs this date as a {@code String} using the formatter. - *

    - * The default implementation must behave as follows: - *

    -     *  return formatter.format(this);
    -     * 
    - * - * @param formatter the formatter to use, not null - * @return the formatted date string, not null - * @throws DateTimeException if an error occurs during printing - */ - public default String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ChronoLocalDateTime.java --- a/src/share/classes/java/time/chrono/ChronoLocalDateTime.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ChronoLocalDateTime.java Sat Apr 13 21:51:36 2013 +0100 @@ -73,7 +73,6 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -119,29 +118,57 @@ extends Temporal, TemporalAdjuster, Comparable> { /** - * Comparator for two {@code ChronoLocalDateTime} instances ignoring the chronology. + * Gets a comparator that compares {@code ChronoLocalDateTime} in + * time-line order ignoring the chronology. *

    - * This method differs from the comparison in {@link #compareTo} in that it - * only compares the underlying date and not the chronology. + * This comparator differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date-time and not the chronology. * This allows dates in different calendar systems to be compared based - * on the time-line position. + * on the position of the date-time on the local time-line. + * The underlying comparison is equivalent to comparing the epoch-day and nano-of-day. * * @see #isAfter * @see #isBefore * @see #isEqual */ - Comparator> DATE_TIME_COMPARATOR = - new Comparator>() { - @Override - public int compare(ChronoLocalDateTime datetime1, ChronoLocalDateTime datetime2) { - int cmp = Long.compare(datetime1.toLocalDate().toEpochDay(), datetime2.toLocalDate().toEpochDay()); - if (cmp == 0) { - cmp = Long.compare(datetime1.toLocalTime().toNanoOfDay(), datetime2.toLocalTime().toNanoOfDay()); - } - return cmp; + static Comparator> timeLineOrder() { + return Chronology.DATE_TIME_ORDER; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ChronoLocalDateTime} from a temporal object. + *

    + * This obtains a local date-time based on the specified temporal. + * A {@code TemporalAccessor} represents an arbitrary set of date and time information, + * which this factory converts to an instance of {@code ChronoLocalDateTime}. + *

    + * The conversion extracts and combines the chronology and the date-time + * from the temporal object. The behavior is equivalent to using + * {@link Chronology#localDateTime(TemporalAccessor)} with the extracted chronology. + * Implementations are permitted to perform optimizations such as accessing + * those fields that are equivalent to the relevant objects. + *

    + * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code ChronoLocalDateTime::from}. + * + * @param temporal the temporal object to convert, not null + * @return the date-time, not null + * @throws DateTimeException if unable to convert to a {@code ChronoLocalDateTime} + * @see Chronology#localDateTime(TemporalAccessor) + */ + static ChronoLocalDateTime from(TemporalAccessor temporal) { + if (temporal instanceof ChronoLocalDateTime) { + return (ChronoLocalDateTime) temporal; } - }; + Chronology chrono = temporal.query(TemporalQuery.chronology()); + if (chrono == null) { + throw new DateTimeException("Unable to obtain ChronoLocalDateTime from TemporalAccessor: " + temporal.getClass()); + } + return chrono.localDateTime(temporal); + } + //----------------------------------------------------------------------- /** * Gets the local date part of this date-time. *

    @@ -163,7 +190,7 @@ LocalTime toLocalTime(); @Override // Override to provide javadoc - public boolean isSupported(TemporalField field); + boolean isSupported(TemporalField field); //----------------------------------------------------------------------- // override for covariant return type @@ -173,7 +200,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoLocalDateTime with(TemporalAdjuster adjuster) { + default ChronoLocalDateTime with(TemporalAdjuster adjuster) { return (ChronoLocalDateTime)(toLocalDate().getChronology().ensureChronoLocalDateTime(Temporal.super.with(adjuster))); } @@ -191,7 +218,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoLocalDateTime plus(TemporalAmount amount) { + default ChronoLocalDateTime plus(TemporalAmount amount) { return (ChronoLocalDateTime)(toLocalDate().getChronology().ensureChronoLocalDateTime(Temporal.super.plus(amount))); } @@ -209,7 +236,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoLocalDateTime minus(TemporalAmount amount) { + default ChronoLocalDateTime minus(TemporalAmount amount) { return (ChronoLocalDateTime)(toLocalDate().getChronology().ensureChronoLocalDateTime(Temporal.super.minus(amount))); } @@ -219,7 +246,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoLocalDateTime minus(long amountToSubtract, TemporalUnit unit) { + default ChronoLocalDateTime minus(long amountToSubtract, TemporalUnit unit) { return (ChronoLocalDateTime)(toLocalDate().getChronology().ensureChronoLocalDateTime(Temporal.super.minus(amountToSubtract, unit))); } @@ -244,14 +271,14 @@ */ @SuppressWarnings("unchecked") @Override - public default R query(TemporalQuery query) { - if (query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + default R query(TemporalQuery query) { + if (query == TemporalQuery.zoneId() || query == TemporalQuery.zone() || query == TemporalQuery.offset()) { return null; - } else if (query == Queries.localTime()) { + } else if (query == TemporalQuery.localTime()) { return (R) toLocalTime(); - } else if (query == Queries.chronology()) { + } else if (query == TemporalQuery.chronology()) { return (R) toLocalDate().getChronology(); - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) NANOS; } // inline TemporalAccessor.super.query(query) as an optimization @@ -285,12 +312,31 @@ * @throws ArithmeticException if numeric overflow occurs */ @Override - public default Temporal adjustInto(Temporal temporal) { + default Temporal adjustInto(Temporal temporal) { return temporal .with(EPOCH_DAY, toLocalDate().toEpochDay()) .with(NANO_OF_DAY, toLocalTime().toNanoOfDay()); } + /** + * Formats this date-time using the specified formatter. + *

    + * This date-time will be passed to the formatter to produce a string. + *

    + * The default implementation must behave as follows: + *

    +     *  return formatter.format(this);
    +     * 
    + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + default String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Combines this time with a time-zone to create a {@code ChronoZonedDateTime}. @@ -334,7 +380,7 @@ * @param offset the offset to use for the conversion, not null * @return an {@code Instant} representing the same instant, not null */ - public default Instant toInstant(ZoneOffset offset) { + default Instant toInstant(ZoneOffset offset) { return Instant.ofEpochSecond(toEpochSecond(offset), toLocalTime().getNano()); } @@ -352,7 +398,7 @@ * @param offset the offset to use for the conversion, not null * @return the number of seconds from the epoch of 1970-01-01T00:00:00Z */ - public default long toEpochSecond(ZoneOffset offset) { + default long toEpochSecond(ZoneOffset offset) { Objects.requireNonNull(offset, "offset"); long epochDay = toLocalDate().toEpochDay(); long secs = epochDay * 86400 + toLocalTime().toSecondOfDay(); @@ -388,7 +434,7 @@ * @return the comparator value, negative if less, positive if greater */ @Override - public default int compareTo(ChronoLocalDateTime other) { + default int compareTo(ChronoLocalDateTime other) { int cmp = toLocalDate().compareTo(other.toLocalDate()); if (cmp == 0) { cmp = toLocalTime().compareTo(other.toLocalTime()); @@ -413,7 +459,7 @@ * @param other the other date-time to compare to, not null * @return true if this is after the specified date-time */ - public default boolean isAfter(ChronoLocalDateTime other) { + default boolean isAfter(ChronoLocalDateTime other) { long thisEpDay = this.toLocalDate().toEpochDay(); long otherEpDay = other.toLocalDate().toEpochDay(); return thisEpDay > otherEpDay || @@ -434,7 +480,7 @@ * @param other the other date-time to compare to, not null * @return true if this is before the specified date-time */ - public default boolean isBefore(ChronoLocalDateTime other) { + default boolean isBefore(ChronoLocalDateTime other) { long thisEpDay = this.toLocalDate().toEpochDay(); long otherEpDay = other.toLocalDate().toEpochDay(); return thisEpDay < otherEpDay || @@ -455,7 +501,7 @@ * @param other the other date-time to compare to, not null * @return true if the underlying date-time is equal to the specified date-time on the timeline */ - public default boolean isEqual(ChronoLocalDateTime other) { + default boolean isEqual(ChronoLocalDateTime other) { // Do the time check first, it is cheaper than computing EPOCH day. return this.toLocalTime().toNanoOfDay() == other.toLocalTime().toNanoOfDay() && this.toLocalDate().toEpochDay() == other.toLocalDate().toEpochDay(); @@ -484,27 +530,11 @@ /** * Outputs this date-time as a {@code String}. *

    - * The output will include the full local date-time and the chronology ID. + * The output will include the full local date-time. * * @return a string representation of this date-time, not null */ @Override String toString(); - /** - * Outputs this date-time as a {@code String} using the formatter. - *

    - * The default implementation must behave as follows: - *

    -     *  return formatter.format(this);
    -     * 
    - * - * @param formatter the formatter to use, not null - * @return the formatted date-time string, not null - * @throws DateTimeException if an error occurs during printing - */ - public default String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ChronoLocalDateTimeImpl.java --- a/src/share/classes/java/time/chrono/ChronoLocalDateTimeImpl.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ChronoLocalDateTimeImpl.java Sat Apr 13 21:51:36 2013 +0100 @@ -171,6 +171,7 @@ * @param time the local time, not null * @return the local date-time, not null */ + @SuppressWarnings("rawtypes") static ChronoLocalDateTimeImpl of(ChronoLocalDate date, LocalTime time) { return new ChronoLocalDateTimeImpl(date, time); } @@ -201,8 +202,8 @@ return this; } // Validate that the new Temporal is a ChronoLocalDate (and not something else) - D cd = (D)date.getChronology().ensureChronoLocalDate(newDate); - return new ChronoLocalDateTimeImpl<>((D)cd, newTime); + D cd = (D) date.getChronology().ensureChronoLocalDate(newDate); + return new ChronoLocalDateTimeImpl<>(cd, newTime); } //----------------------------------------------------------------------- @@ -221,7 +222,7 @@ public boolean isSupported(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return f.isDateField() || f.isTimeField(); + return f.isDateBased() || f.isTimeBased(); } return field != null && field.isSupportedBy(this); } @@ -230,7 +231,7 @@ public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return (f.isTimeField() ? time.range(field) : date.range(field)); + return (f.isTimeBased() ? time.range(field) : date.range(field)); } return field.rangeRefinedBy(this); } @@ -239,7 +240,7 @@ public int get(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return (f.isTimeField() ? time.get(field) : date.get(field)); + return (f.isTimeBased() ? time.get(field) : date.get(field)); } return range(field).checkValidIntValue(getLong(field), field); } @@ -248,7 +249,7 @@ public long getLong(TemporalField field) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - return (f.isTimeField() ? time.getLong(field) : date.getLong(field)); + return (f.isTimeBased() ? time.getLong(field) : date.getLong(field)); } return field.getFrom(this); } @@ -272,7 +273,7 @@ public ChronoLocalDateTimeImpl with(TemporalField field, long newValue) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - if (f.isTimeField()) { + if (f.isTimeBased()) { return with(date, time.with(field, newValue)); } else { return with(date.with(field, newValue), time); @@ -376,7 +377,7 @@ } D endDate = end.toLocalDate(); if (end.toLocalTime().isBefore(time)) { - endDate = (D)endDate.minus(1, ChronoUnit.DAYS); + endDate = endDate.minus(1, ChronoUnit.DAYS); } return date.periodUntil(endDate, unit); } @@ -403,7 +404,7 @@ } static ChronoLocalDateTime readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - ChronoLocalDate date = (ChronoLocalDate) in.readObject(); + ChronoLocalDate date = (ChronoLocalDate) in.readObject(); LocalTime time = (LocalTime) in.readObject(); return date.atTime(time); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ChronoZonedDateTime.java --- a/src/share/classes/java/time/chrono/ChronoZonedDateTime.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ChronoZonedDateTime.java Sat Apr 13 21:51:36 2013 +0100 @@ -73,7 +73,6 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -81,6 +80,7 @@ import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Comparator; import java.util.Objects; @@ -119,30 +119,59 @@ extends Temporal, Comparable> { /** - * Comparator for two {@code ChronoZonedDateTime} instances ignoring the chronology. + * Gets a comparator that compares {@code ChronoZonedDateTime} in + * time-line order ignoring the chronology. *

    - * This method differs from the comparison in {@link #compareTo} in that it - * only compares the underlying date and not the chronology. + * This comparator differs from the comparison in {@link #compareTo} in that it + * only compares the underlying instant and not the chronology. * This allows dates in different calendar systems to be compared based - * on the time-line position. + * on the position of the date-time on the instant time-line. + * The underlying comparison is equivalent to comparing the epoch-second and nano-of-second. * * @see #isAfter * @see #isBefore * @see #isEqual */ - Comparator> INSTANT_COMPARATOR = new Comparator>() { - @Override - public int compare(ChronoZonedDateTime datetime1, ChronoZonedDateTime datetime2) { - int cmp = Long.compare(datetime1.toEpochSecond(), datetime2.toEpochSecond()); - if (cmp == 0) { - cmp = Long.compare(datetime1.toLocalTime().toNanoOfDay(), datetime2.toLocalTime().toNanoOfDay()); - } - return cmp; + static Comparator> timeLineOrder() { + return Chronology.INSTANT_ORDER; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ChronoZonedDateTime} from a temporal object. + *

    + * This creates a zoned date-time based on the specified temporal. + * A {@code TemporalAccessor} represents an arbitrary set of date and time information, + * which this factory converts to an instance of {@code ChronoZonedDateTime}. + *

    + * The conversion extracts and combines the chronology, date, time and zone + * from the temporal object. The behavior is equivalent to using + * {@link Chronology#zonedDateTime(TemporalAccessor)} with the extracted chronology. + * Implementations are permitted to perform optimizations such as accessing + * those fields that are equivalent to the relevant objects. + *

    + * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code ChronoZonedDateTime::from}. + * + * @param temporal the temporal objec t to convert, not null + * @return the date-time, not null + * @throws DateTimeException if unable to convert to a {@code ChronoZonedDateTime} + * @see Chronology#zonedDateTime(TemporalAccessor) + */ + static ChronoZonedDateTime from(TemporalAccessor temporal) { + if (temporal instanceof ChronoZonedDateTime) { + return (ChronoZonedDateTime) temporal; } - }; + Chronology chrono = temporal.query(TemporalQuery.chronology()); + if (chrono == null) { + throw new DateTimeException("Unable to obtain ChronoZonedDateTime from TemporalAccessor: " + temporal.getClass()); + } + return chrono.zonedDateTime(temporal); + } + //----------------------------------------------------------------------- @Override - public default ValueRange range(TemporalField field) { + default ValueRange range(TemporalField field) { if (field instanceof ChronoField) { if (field == INSTANT_SECONDS || field == OFFSET_SECONDS) { return field.range(); @@ -153,11 +182,13 @@ } @Override - public default int get(TemporalField field) { + default int get(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { - case INSTANT_SECONDS: throw new DateTimeException("Field too large for an int: " + field); - case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + case INSTANT_SECONDS: + throw new UnsupportedTemporalTypeException("Invalid field 'InstantSeconds' for get() method, use getLong() instead"); + case OFFSET_SECONDS: + return getOffset().getTotalSeconds(); } return toLocalDateTime().get(field); } @@ -165,7 +196,7 @@ } @Override - public default long getLong(TemporalField field) { + default long getLong(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { case INSTANT_SECONDS: return toEpochSecond(); @@ -184,7 +215,7 @@ * * @return the date part of this date-time, not null */ - public default D toLocalDate() { + default D toLocalDate() { return toLocalDateTime().toLocalDate(); } @@ -196,7 +227,7 @@ * * @return the time part of this date-time, not null */ - public default LocalTime toLocalTime() { + default LocalTime toLocalTime() { return toLocalDateTime().toLocalTime(); } @@ -306,7 +337,7 @@ ChronoZonedDateTime withZoneSameInstant(ZoneId zone); @Override // Override to provide javadoc - public boolean isSupported(TemporalField field); + boolean isSupported(TemporalField field); //----------------------------------------------------------------------- // override for covariant return type @@ -316,7 +347,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoZonedDateTime with(TemporalAdjuster adjuster) { + default ChronoZonedDateTime with(TemporalAdjuster adjuster) { return (ChronoZonedDateTime)(toLocalDate().getChronology().ensureChronoZonedDateTime(Temporal.super.with(adjuster))); } @@ -334,7 +365,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoZonedDateTime plus(TemporalAmount amount) { + default ChronoZonedDateTime plus(TemporalAmount amount) { return (ChronoZonedDateTime)(toLocalDate().getChronology().ensureChronoZonedDateTime(Temporal.super.plus(amount))); } @@ -352,7 +383,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoZonedDateTime minus(TemporalAmount amount) { + default ChronoZonedDateTime minus(TemporalAmount amount) { return (ChronoZonedDateTime)(toLocalDate().getChronology().ensureChronoZonedDateTime(Temporal.super.minus(amount))); } @@ -362,7 +393,7 @@ * @throws ArithmeticException {@inheritDoc} */ @Override - public default ChronoZonedDateTime minus(long amountToSubtract, TemporalUnit unit) { + default ChronoZonedDateTime minus(long amountToSubtract, TemporalUnit unit) { return (ChronoZonedDateTime)(toLocalDate().getChronology().ensureChronoZonedDateTime(Temporal.super.minus(amountToSubtract, unit))); } @@ -387,16 +418,16 @@ */ @SuppressWarnings("unchecked") @Override - public default R query(TemporalQuery query) { - if (query == Queries.zone() || query == Queries.zoneId()) { + default R query(TemporalQuery query) { + if (query == TemporalQuery.zone() || query == TemporalQuery.zoneId()) { return (R) getZone(); - } else if (query == Queries.offset()) { + } else if (query == TemporalQuery.offset()) { return (R) getOffset(); - } else if (query == Queries.localTime()) { + } else if (query == TemporalQuery.localTime()) { return (R) toLocalTime(); - } else if (query == Queries.chronology()) { + } else if (query == TemporalQuery.chronology()) { return (R) toLocalDate().getChronology(); - } else if (query == Queries.precision()) { + } else if (query == TemporalQuery.precision()) { return (R) NANOS; } // inline TemporalAccessor.super.query(query) as an optimization @@ -404,6 +435,25 @@ return query.queryFrom(this); } + /** + * Formats this date-time using the specified formatter. + *

    + * This date-time will be passed to the formatter to produce a string. + *

    + * The default implementation must behave as follows: + *

    +     *  return formatter.format(this);
    +     * 
    + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + default String format(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.format(this); + } + //----------------------------------------------------------------------- /** * Converts this date-time to an {@code Instant}. @@ -415,7 +465,7 @@ * * @return an {@code Instant} representing the same instant, not null */ - public default Instant toInstant() { + default Instant toInstant() { return Instant.ofEpochSecond(toEpochSecond(), toLocalTime().getNano()); } @@ -430,7 +480,7 @@ * * @return the number of seconds from the epoch of 1970-01-01T00:00:00Z */ - public default long toEpochSecond() { + default long toEpochSecond() { long epochDay = toLocalDate().toEpochDay(); long secs = epochDay * 86400 + toLocalTime().toSecondOfDay(); secs -= getOffset().getTotalSeconds(); @@ -454,7 +504,7 @@ * @return the comparator value, negative if less, positive if greater */ @Override - public default int compareTo(ChronoZonedDateTime other) { + default int compareTo(ChronoZonedDateTime other) { int cmp = Long.compare(toEpochSecond(), other.toEpochSecond()); if (cmp == 0) { cmp = toLocalTime().getNano() - other.toLocalTime().getNano(); @@ -484,7 +534,7 @@ * @param other the other date-time to compare to, not null * @return true if this point is before the specified date-time */ - public default boolean isBefore(ChronoZonedDateTime other) { + default boolean isBefore(ChronoZonedDateTime other) { long thisEpochSec = toEpochSecond(); long otherEpochSec = other.toEpochSecond(); return thisEpochSec < otherEpochSec || @@ -504,7 +554,7 @@ * @param other the other date-time to compare to, not null * @return true if this is after the specified date-time */ - public default boolean isAfter(ChronoZonedDateTime other) { + default boolean isAfter(ChronoZonedDateTime other) { long thisEpochSec = toEpochSecond(); long otherEpochSec = other.toEpochSecond(); return thisEpochSec > otherEpochSec || @@ -524,7 +574,7 @@ * @param other the other date-time to compare to, not null * @return true if the instant equals the instant of the specified date-time */ - public default boolean isEqual(ChronoZonedDateTime other) { + default boolean isEqual(ChronoZonedDateTime other) { return toEpochSecond() == other.toEpochSecond() && toLocalTime().getNano() == other.toLocalTime().getNano(); } @@ -555,28 +605,11 @@ /** * Outputs this date-time as a {@code String}. *

    - * The output will include the full zoned date-time and the chronology ID. + * The output will include the full zoned date-time. * * @return a string representation of this date-time, not null */ @Override String toString(); - /** - * Outputs this date-time as a {@code String} using the formatter. - *

    - * The default implementation must behave as follows: - *

    -     *  return formatter.format(this);
    -     * 
    - * - * @param formatter the formatter to use, not null - * @return the formatted date-time string, not null - * @throws DateTimeException if an error occurs during printing - */ - public default String toString(DateTimeFormatter formatter) { - Objects.requireNonNull(formatter, "formatter"); - return formatter.format(this); - } - } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ChronoZonedDateTimeImpl.java --- a/src/share/classes/java/time/chrono/ChronoZonedDateTimeImpl.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ChronoZonedDateTimeImpl.java Sat Apr 13 21:51:36 2013 +0100 @@ -167,6 +167,7 @@ * @param zone the zone identifier, not null * @return the zoned date-time, not null */ + @SuppressWarnings("rawtypes") static ChronoZonedDateTimeImpl ofInstant(Chronology chrono, Instant instant, ZoneId zone) { ZoneRules rules = zone.getRules(); ZoneOffset offset = rules.getOffset(instant); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/Chronology.java --- a/src/share/classes/java/time/chrono/Chronology.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/Chronology.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,38 +61,57 @@ */ package java.time.chrono; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.TemporalAdjuster.nextOrSame; + import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectStreamException; +import java.io.Serializable; import java.time.Clock; import java.time.DateTimeException; +import java.time.DayOfWeek; import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; -import java.time.chrono.HijrahChronology; -import java.time.chrono.JapaneseChronology; -import java.time.chrono.MinguoChronology; -import java.time.chrono.ThaiBuddhistChronology; import java.time.format.DateTimeFormatterBuilder; +import java.time.format.ResolverStyle; import java.time.format.TextStyle; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; +import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import sun.util.logging.PlatformLogger; + /** * A calendar system, used to organize and identify dates. *

    @@ -106,7 +125,7 @@ * for use by any {@code Chronology} implementation: *

      *   LocalDate isoDate = ...
    - *   ChronoLocalDate<ThaiBuddhistChronology> thaiDate = ...
    + *   ThaiBuddhistDate thaiDate = ...
      *   int isoYear = isoDate.get(ChronoField.YEAR);
      *   int thaiYear = thaiDate.get(ChronoField.YEAR);
      * 
    @@ -154,8 +173,8 @@ *

    * Each chronology must define a chronology ID that is unique within the system. * If the chronology represents a calendar system defined by the - * Unicode Locale Data Markup Language (LDML) specification then that - * calendar type should also be specified. + * CLDR specification then the calendar type is the concatenation of the + * CLDR type and, if applicable, the CLDR variant, * *

    Specification for implementors

    * This class must be implemented with care to ensure other classes operate correctly. @@ -167,6 +186,36 @@ public abstract class Chronology implements Comparable { /** + * ChronoLocalDate order constant. + */ + static final Comparator> DATE_ORDER = + (Comparator> & Serializable) (date1, date2) -> { + return Long.compare(date1.toEpochDay(), date2.toEpochDay()); + }; + /** + * ChronoLocalDateTime order constant. + */ + static final Comparator> DATE_TIME_ORDER = + (Comparator> & Serializable) (dateTime1, dateTime2) -> { + int cmp = Long.compare(dateTime1.toLocalDate().toEpochDay(), dateTime2.toLocalDate().toEpochDay()); + if (cmp == 0) { + cmp = Long.compare(dateTime1.toLocalTime().toNanoOfDay(), dateTime2.toLocalTime().toNanoOfDay()); + } + return cmp; + }; + /** + * ChronoZonedDateTime order constant. + */ + static final Comparator> INSTANT_ORDER = + (Comparator> & Serializable) (dateTime1, dateTime2) -> { + int cmp = Long.compare(dateTime1.toEpochSecond(), dateTime2.toEpochSecond()); + if (cmp == 0) { + cmp = Long.compare(dateTime1.toLocalTime().getNano(), dateTime2.toLocalTime().getNano()); + } + return cmp; + }; + + /** * Map of available calendars by ID. */ private static final ConcurrentHashMap CHRONOS_BY_ID = new ConcurrentHashMap<>(); @@ -176,19 +225,35 @@ private static final ConcurrentHashMap CHRONOS_BY_TYPE = new ConcurrentHashMap<>(); /** + * Register a Chronology by its ID and type for lookup by {@link #of(java.lang.String)}. + * Chronologies must not be registered until they are completely constructed. + * Specifically, not in the constructor of Chronology. + * + * @param chrono the chronology to register; not null + * @return the already registered Chronology if any, may be null + */ + static Chronology registerChrono(Chronology chrono) { + return registerChrono(chrono, chrono.getId()); + } + + /** * Register a Chronology by ID and type for lookup by {@link #of(java.lang.String)}. * Chronos must not be registered until they are completely constructed. * Specifically, not in the constructor of Chronology. + * * @param chrono the chronology to register; not null + * @param id the ID to register the chronology; not null + * @return the already registered Chronology if any, may be null */ - private static void registerChrono(Chronology chrono) { - Chronology prev = CHRONOS_BY_ID.putIfAbsent(chrono.getId(), chrono); + static Chronology registerChrono(Chronology chrono, String id) { + Chronology prev = CHRONOS_BY_ID.putIfAbsent(id, chrono); if (prev == null) { String type = chrono.getCalendarType(); if (type != null) { CHRONOS_BY_TYPE.putIfAbsent(type, chrono); } } + return prev; } /** @@ -209,18 +274,25 @@ private static boolean initCache() { if (CHRONOS_BY_ID.get("ISO") == null) { // Initialization is incomplete - @SuppressWarnings("rawtypes") - ServiceLoader loader = ServiceLoader.load(Chronology.class, null); - for (Chronology chrono : loader) { - registerChrono(chrono); - } - // Register these calendars; the ServiceLoader configuration is not used + // Register built-in Chronologies registerChrono(HijrahChronology.INSTANCE); registerChrono(JapaneseChronology.INSTANCE); registerChrono(MinguoChronology.INSTANCE); registerChrono(ThaiBuddhistChronology.INSTANCE); + // Register Chronologies from the ServiceLoader + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chronology.class, null); + for (Chronology chrono : loader) { + String id = chrono.getId(); + if (id.equals("ISO") || registerChrono(chrono) != null) { + // Log the attempt to replace an existing Chronology + PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); + logger.warning("Ignoring duplicate Chronology, from ServiceLoader configuration " + id); + } + } + // finally, register IsoChronology to mark initialization is complete registerChrono(IsoChronology.INSTANCE); return true; @@ -236,7 +308,7 @@ * A {@code TemporalAccessor} represents an arbitrary set of date and time information, * which this factory converts to an instance of {@code Chronology}. *

    - * The conversion will obtain the chronology using {@link Queries#chronology()}. + * The conversion will obtain the chronology using {@link TemporalQuery#chronology()}. * If the specified temporal object does not have a chronology, {@link IsoChronology} is returned. *

    * This method matches the signature of the functional interface {@link TemporalQuery} @@ -248,7 +320,7 @@ */ public static Chronology from(TemporalAccessor temporal) { Objects.requireNonNull(temporal, "temporal"); - Chronology obj = temporal.query(Queries.chronology()); + Chronology obj = temporal.query(TemporalQuery.chronology()); return (obj != null ? obj : IsoChronology.INSTANCE); } @@ -266,13 +338,16 @@ *

    * The {@code Locale} class also supports an extension mechanism that * can be used to identify a calendar system. The mechanism is a form - * of key-value pairs, where the calendar system has the key "ca". + * of key-value pairs, where the calendar system has the key "ca" + * and an optional variant key "cv". * For example, the locale "en-JP-u-ca-japanese" represents the English * language as used in Japan with the Japanese calendar system. *

    * This method finds the desired calendar system by in a manner equivalent * to passing "ca" to {@link Locale#getUnicodeLocaleType(String)}. * If the "ca" key is not present, then {@code IsoChronology} is returned. + * The variant, if present, is appended to the "ca" value separated by "-" + * and the concatenated value is used to find the calendar system by type. *

    * Note that the behavior of this method differs from the older * {@link java.util.Calendar#getInstance(Locale)} method. @@ -299,6 +374,10 @@ if (type == null || "iso".equals(type) || "iso8601".equals(type)) { return IsoChronology.INSTANCE; } + String variant = locale.getUnicodeLocaleType("cv"); + if (variant != null && !variant.isEmpty()) { + type = type + '-' + variant; + } // Not pre-defined; lookup by the type do { Chronology chrono = CHRONOS_BY_TYPE.get(type); @@ -307,6 +386,16 @@ } // If not found, do the initialization (once) and repeat the lookup } while (initCache()); + + // Look for a Chronology using ServiceLoader of the Thread's ContextClassLoader + // Application provided Chronologies must not be cached + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chronology.class); + for (Chronology chrono : loader) { + if (type.equals(chrono.getCalendarType())) { + return chrono; + } + } throw new DateTimeException("Unknown calendar system: " + type); } @@ -317,7 +406,8 @@ *

    * This returns a chronology based on either the ID or the type. * The {@link #getId() chronology ID} uniquely identifies the chronology. - * The {@link #getCalendarType() calendar system type} is defined by the LDML specification. + * The {@link #getCalendarType() calendar system type} is defined by the + * CLDR specification. *

    * The chronology may be a system chronology or a chronology * provided by the application via ServiceLoader configuration. @@ -379,7 +469,7 @@ */ public static Set getAvailableChronologies() { initCache(); // force initialization - HashSet chronos = new HashSet(CHRONOS_BY_ID.values()); + HashSet chronos = new HashSet<>(CHRONOS_BY_ID.values()); /// Add in Chronologies from the ServiceLoader configuration @SuppressWarnings("rawtypes") @@ -406,9 +496,9 @@ * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDate * or the chronology is not equal this Chronology */ - ChronoLocalDate ensureChronoLocalDate(Temporal temporal) { + ChronoLocalDate ensureChronoLocalDate(Temporal temporal) { @SuppressWarnings("unchecked") - ChronoLocalDate other = (ChronoLocalDate) temporal; + ChronoLocalDate other = (ChronoLocalDate) temporal; if (this.equals(other.getChronology()) == false) { throw new ClassCastException("Chronology mismatch, expected: " + getId() + ", actual: " + other.getChronology().getId()); } @@ -464,15 +554,16 @@ public abstract String getId(); /** - * Gets the calendar type of the underlying calendar system. + * Gets the calendar type of the calendar system. *

    - * The calendar type is an identifier defined by the - * Unicode Locale Data Markup Language (LDML) specification. - * It can be used to lookup the {@code Chronology} using {@link #of(String)}. - * It can also be used as part of a locale, accessible via - * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * The calendar type is an identifier defined by the CLDR and + * Unicode Locale Data Markup Language (LDML) specifications + * to uniquely identification a calendar. + * The {@code getCalendarType} is the concatenation of the CLDR calendar type + * and the variant, if applicable, is appended separated by "-". + * The calendar type is used to lookup the {@code Chronology} using {@link #of(String)}. * - * @return the calendar system type, null if the calendar is not defined by LDML + * @return the calendar system type, null if the calendar is not defined * @see #getId() */ public abstract String getCalendarType(); @@ -488,8 +579,9 @@ * @param dayOfMonth the chronology day-of-month * @return the local date in this chronology, not null * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not of the correct type for the chronology */ - public ChronoLocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + public ChronoLocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) { return date(prolepticYear(era, yearOfEra), month, dayOfMonth); } @@ -503,7 +595,7 @@ * @return the local date in this chronology, not null * @throws DateTimeException if unable to create the date */ - public abstract ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth); + public abstract ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth); /** * Obtains a local date in this chronology from the era, year-of-era and @@ -514,8 +606,9 @@ * @param dayOfYear the chronology day-of-year * @return the local date in this chronology, not null * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not of the correct type for the chronology */ - public ChronoLocalDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + public ChronoLocalDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); } @@ -528,7 +621,19 @@ * @return the local date in this chronology, not null * @throws DateTimeException if unable to create the date */ - public abstract ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear); + public abstract ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear); + + /** + * Obtains a local date in this chronology from the epoch-day. + *

    + * The definition of {@link ChronoField#EPOCH_DAY EPOCH_DAY} is the same + * for all calendar systems, thus it can be used for conversion. + * + * @param epochDay the epoch day + * @return the local date in this chronology, not null + * @throws DateTimeException if unable to create the date + */ + public abstract ChronoLocalDate dateEpochDay(long epochDay); //----------------------------------------------------------------------- /** @@ -545,7 +650,7 @@ * @return the current local date using the system clock and default time-zone, not null * @throws DateTimeException if unable to create the date */ - public ChronoLocalDate dateNow() { + public ChronoLocalDate dateNow() { return dateNow(Clock.systemDefaultZone()); } @@ -562,7 +667,7 @@ * @return the current local date using the system clock, not null * @throws DateTimeException if unable to create the date */ - public ChronoLocalDate dateNow(ZoneId zone) { + public ChronoLocalDate dateNow(ZoneId zone) { return dateNow(Clock.system(zone)); } @@ -577,7 +682,7 @@ * @return the current local date, not null * @throws DateTimeException if unable to create the date */ - public ChronoLocalDate dateNow(Clock clock) { + public ChronoLocalDate dateNow(Clock clock) { Objects.requireNonNull(clock, "clock"); return date(LocalDate.now(clock)); } @@ -586,7 +691,7 @@ /** * Obtains a local date in this chronology from another temporal object. *

    - * This creates a date in this chronology based on the specified temporal. + * This obtains a date in this chronology based on the specified temporal. * A {@code TemporalAccessor} represents an arbitrary set of date and time information, * which this factory converts to an instance of {@code ChronoLocalDate}. *

    @@ -599,13 +704,14 @@ * @param temporal the temporal object to convert, not null * @return the local date in this chronology, not null * @throws DateTimeException if unable to create the date + * @see ChronoLocalDate#from(TemporalAccessor) */ - public abstract ChronoLocalDate date(TemporalAccessor temporal); + public abstract ChronoLocalDate date(TemporalAccessor temporal); /** * Obtains a local date-time in this chronology from another temporal object. *

    - * This creates a date-time in this chronology based on the specified temporal. + * This obtains a date-time in this chronology based on the specified temporal. * A {@code TemporalAccessor} represents an arbitrary set of date and time information, * which this factory converts to an instance of {@code ChronoLocalDateTime}. *

    @@ -621,6 +727,7 @@ * @param temporal the temporal object to convert, not null * @return the local date-time in this chronology, not null * @throws DateTimeException if unable to create the date-time + * @see ChronoLocalDateTime#from(TemporalAccessor) */ public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { try { @@ -633,7 +740,7 @@ /** * Obtains a {@code ChronoZonedDateTime} in this chronology from another temporal object. *

    - * This creates a zoned date-time in this chronology based on the specified temporal. + * This obtains a zoned date-time in this chronology based on the specified temporal. * A {@code TemporalAccessor} represents an arbitrary set of date and time information, * which this factory converts to an instance of {@code ChronoZonedDateTime}. *

    @@ -652,6 +759,7 @@ * @param temporal the temporal object to convert, not null * @return the zoned date-time in this chronology, not null * @throws DateTimeException if unable to create the date-time + * @see ChronoZonedDateTime#from(TemporalAccessor) */ public ChronoZonedDateTime zonedDateTime(TemporalAccessor temporal) { try { @@ -661,6 +769,7 @@ return zonedDateTime(instant, zone); } catch (DateTimeException ex1) { + @SuppressWarnings("rawtypes") ChronoLocalDateTimeImpl cldt = ensureChronoLocalDateTime(localDateTime(temporal)); return ChronoZonedDateTimeImpl.ofBest(cldt, zone, null); } @@ -672,7 +781,7 @@ /** * Obtains a {@code ChronoZonedDateTime} in this chronology from an {@code Instant}. *

    - * This creates a zoned date-time with the same instant as that specified. + * This obtains a zoned date-time with the same instant as that specified. * * @param instant the instant to create the date-time from, not null * @param zone the time-zone, not null @@ -703,11 +812,17 @@ * Calculates the proleptic-year given the era and year-of-era. *

    * This combines the era and year-of-era into the single proleptic-year field. + *

    + * If the chronology makes active use of eras, such as {@code JapaneseChronology} + * then the year-of-era will be validated against the era. + * For other chronologies, validation is optional. * * @param era the era of the correct type for the chronology, not null * @param yearOfEra the chronology year-of-era * @return the proleptic-year - * @throws DateTimeException if unable to convert + * @throws DateTimeException if unable to convert to a proleptic-year, + * such as if the year is invalid for the era + * @throws ClassCastException if the {@code era} is not of the correct type for the chronology */ public abstract int prolepticYear(Era era, int yearOfEra); @@ -775,24 +890,175 @@ * @return the text value of the chronology, not null */ public String getDisplayName(TextStyle style, Locale locale) { - return new DateTimeFormatterBuilder().appendChronologyText(style).toFormatter(locale).format(new TemporalAccessor() { + return new DateTimeFormatterBuilder().appendChronologyText(style).toFormatter(locale).format(toTemporal()); + } + + /** + * Converts this chronology to a {@code TemporalAccessor}. + *

    + * A {@code Chronology} can be fully represented as a {@code TemporalAccessor}. + * However, the interface is not implemented by this class as most of the + * methods on the interface have no meaning to {@code Chronology}. + *

    + * The returned temporal has no supported fields, with the query method + * supporting the return of the chronology using {@link TemporalQuery#chronology()}. + * + * @return a temporal equivalent to this chronology, not null + */ + private TemporalAccessor toTemporal() { + return new TemporalAccessor() { @Override public boolean isSupported(TemporalField field) { return false; } @Override public long getLong(TemporalField field) { - throw new DateTimeException("Unsupported field: " + field); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field); } @SuppressWarnings("unchecked") @Override public R query(TemporalQuery query) { - if (query == Queries.chronology()) { + if (query == TemporalQuery.chronology()) { return (R) Chronology.this; } return TemporalAccessor.super.query(query); } - }); + }; + } + + //----------------------------------------------------------------------- + /** + * Resolves parsed {@code ChronoField} values into a date during parsing. + *

    + * Most {@code TemporalField} implementations are resolved using the + * resolve method on the field. By contrast, the {@code ChronoField} class + * defines fields that only have meaning relative to the chronology. + * As such, {@code ChronoField} date fields are resolved here in the + * context of a specific chronology. + *

    + * The default implementation is suitable for most calendar systems. + * If {@link ChronoField#YEAR_OF_ERA} is found without an {@link ChronoField#ERA} + * then the last era in {@link #eras()} is used. + * The implementation assumes a 7 day week, that the first day-of-month + * has the value 1, and that first day-of-year has the value 1. + * + * @param fieldValues the map of fields to values, which can be updated, not null + * @param resolverStyle the requested type of resolve, not null + * @return the resolved date, null if insufficient information to create a date + * @throws DateTimeException if the date cannot be resolved, typically + * because of a conflict in the input data + */ + public ChronoLocalDate resolveDate(Map fieldValues, ResolverStyle resolverStyle) { + // check epoch-day before inventing era + if (fieldValues.containsKey(EPOCH_DAY)) { + return dateEpochDay(fieldValues.remove(EPOCH_DAY)); + } + + // fix proleptic month before inventing era + Long pMonth = fieldValues.remove(PROLEPTIC_MONTH); + if (pMonth != null) { + // first day-of-month is likely to be safest for setting proleptic-month + // cannot add to year zero, as not all chronologies have a year zero + ChronoLocalDate chronoDate = dateNow() + .with(DAY_OF_MONTH, 1).with(PROLEPTIC_MONTH, pMonth); + addFieldValue(fieldValues, MONTH_OF_YEAR, chronoDate.get(MONTH_OF_YEAR)); + addFieldValue(fieldValues, YEAR, chronoDate.get(YEAR)); + } + + // invent era if necessary to resolve year-of-era + Long yoeLong = fieldValues.remove(YEAR_OF_ERA); + if (yoeLong != null) { + Long eraLong = fieldValues.remove(ERA); + int yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA); + if (eraLong != null) { + Era eraObj = eraOf(Math.toIntExact(eraLong)); + addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe)); + } else if (fieldValues.containsKey(YEAR)) { + int year = range(YEAR).checkValidIntValue(fieldValues.get(YEAR), YEAR); + ChronoLocalDate chronoDate = dateYearDay(year, 1); + addFieldValue(fieldValues, YEAR, prolepticYear(chronoDate.getEra(), yoe)); + } else { + List eras = eras(); + if (eras.isEmpty()) { + addFieldValue(fieldValues, YEAR, yoe); + } else { + Era eraObj = eras.get(eras.size() - 1); + addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe)); + } + } + } + + // build date + if (fieldValues.containsKey(YEAR)) { + if (fieldValues.containsKey(MONTH_OF_YEAR)) { + if (fieldValues.containsKey(DAY_OF_MONTH)) { + int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); + int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR); + int dom = range(DAY_OF_MONTH).checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH); + return date(y, moy, dom); + } + if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) { + if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { + int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); + int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR); + int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH); + int ad = range(ALIGNED_DAY_OF_WEEK_IN_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), ALIGNED_DAY_OF_WEEK_IN_MONTH); + ChronoLocalDate chronoDate = date(y, moy, 1); + return chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS); + } + if (fieldValues.containsKey(DAY_OF_WEEK)) { + int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); + int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR); + int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH); + int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK); + ChronoLocalDate chronoDate = date(y, moy, 1); + return chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow))); + } + } + } + if (fieldValues.containsKey(DAY_OF_YEAR)) { + int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); + int doy = range(DAY_OF_YEAR).checkValidIntValue(fieldValues.remove(DAY_OF_YEAR), DAY_OF_YEAR); + return dateYearDay(y, doy); + } + if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) { + if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { + int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); + int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR); + int ad = range(ALIGNED_DAY_OF_WEEK_IN_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), ALIGNED_DAY_OF_WEEK_IN_YEAR); + ChronoLocalDate chronoDate = dateYearDay(y, 1); + return chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS); + } + if (fieldValues.containsKey(DAY_OF_WEEK)) { + int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR); + int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR); + int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK); + ChronoLocalDate chronoDate = dateYearDay(y, 1); + return chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow))); + } + } + } + return null; + } + + /** + * Adds a field-value pair to the map, checking for conflicts. + *

    + * If the field is not already present, then the field-value pair is added to the map. + * If the field is already present and it has the same value as that specified, no action occurs. + * If the field is already present and it has a different value to that specified, then + * an exception is thrown. + * + * @param field the field to add, not null + * @param value the value to add, not null + * @throws DateTimeException if the field is already present with a different value + */ + void addFieldValue(Map fieldValues, ChronoField field, long value) { + Long old = fieldValues.get(field); // check first for better error message + if (old != null && old.longValue() != value) { + throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value); + } + fieldValues.put(field, value); } //----------------------------------------------------------------------- @@ -861,16 +1127,16 @@ //----------------------------------------------------------------------- /** - * Writes the object using a - * dedicated serialized form. + * Writes the Chronology using a + * dedicated serialized form. *

    -     *  out.writeByte(7);  // identifies this as a Chronology
    -     * out.writeUTF(chronoId);
    +     *  out.writeByte(1);  // identifies this as a Chronology
    +     *  out.writeUTF(getId());
          * 
    * * @return the instance of {@code Ser}, not null */ - private Object writeReplace() { + protected Object writeReplace() { return new Ser(Ser.CHRONO_TYPE, this); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/Era.java --- a/src/share/classes/java/time/chrono/Era.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/Era.java Sat Apr 13 21:51:36 2013 +0100 @@ -65,10 +65,10 @@ import static java.time.temporal.ChronoUnit.ERAS; import java.time.DateTimeException; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.format.DateTimeFormatterBuilder; import java.time.format.TextStyle; import java.time.temporal.ChronoField; -import java.time.temporal.Queries; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; @@ -120,55 +120,6 @@ */ int getValue(); - /** - * Gets the chronology of this era. - *

    - * The {@code Chronology} represents the calendar system in use. - * This always returns the standard form of the chronology. - * - * @return the chronology, not null - */ - Chronology getChronology(); - - //----------------------------------------------------------------------- - /** - * Obtains a date in this era given the year-of-era, month, and day. - *

    - * This era is combined with the given date fields to form a date. - * The year specified must be the year-of-era. - * Methods to create a date from the proleptic-year are on {@code Chronology}. - * This always uses the standard form of the chronology. - *

    - * This default implementation invokes the factory method on {@link Chronology}. - * - * @param yearOfEra the calendar system year-of-era - * @param month the calendar system month-of-year - * @param day the calendar system day-of-month - * @return a local date based on this era and the specified year-of-era, month and day - */ - public default ChronoLocalDate date(int yearOfEra, int month, int day) { - return getChronology().date(this, yearOfEra, month, day); - } - - - /** - * Obtains a date in this era given year-of-era and day-of-year fields. - *

    - * This era is combined with the given date fields to form a date. - * The year specified must be the year-of-era. - * Methods to create a date from the proleptic-year are on {@code Chronology}. - * This always uses the standard form of the chronology. - *

    - * This default implementation invokes the factory method on {@link Chronology}. - * - * @param yearOfEra the calendar system year-of-era - * @param dayOfYear the calendar system day-of-year - * @return a local date based on this era and the specified year-of-era and day-of-year - */ - public default ChronoLocalDate dateYearDay(int yearOfEra, int dayOfYear) { - return getChronology().dateYearDay(this, yearOfEra, dayOfYear); - } - //----------------------------------------------------------------------- /** * Checks if the specified field is supported. @@ -190,7 +141,7 @@ * @return true if the field is supported on this era, false if not */ @Override - public default boolean isSupported(TemporalField field) { + default boolean isSupported(TemporalField field) { if (field instanceof ChronoField) { return field == ERA; } @@ -207,19 +158,23 @@ *

    * If the field is a {@link ChronoField} then the query is implemented here. * The {@code ERA} field returns the range. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} * passing {@code this} as the argument. * Whether the range can be obtained is determined by the field. + *

    + * The default implementation must return a range for {@code ERA} from + * zero to one, suitable for two era calendar systems such as ISO. * * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the unit is not supported */ @Override // override for Javadoc - public default ValueRange range(TemporalField field) { + default ValueRange range(TemporalField field) { return TemporalAccessor.super.range(field); } @@ -233,7 +188,7 @@ *

    * If the field is a {@link ChronoField} then the query is implemented here. * The {@code ERA} field returns the value of the era. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -242,15 +197,18 @@ * * @param field the field to get, not null * @return the value for the field - * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ @Override // override for Javadoc and performance - public default int get(TemporalField field) { + default int get(TemporalField field) { if (field == ERA) { return getValue(); } - return range(field).checkValidIntValue(getLong(field), field); + return TemporalAccessor.super.get(field); } /** @@ -262,7 +220,7 @@ *

    * If the field is a {@link ChronoField} then the query is implemented here. * The {@code ERA} field returns the value of the era. - * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -272,14 +230,15 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ @Override - public default long getLong(TemporalField field) { + default long getLong(TemporalField field) { if (field == ERA) { return getValue(); } else if (field instanceof ChronoField) { - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } @@ -305,10 +264,8 @@ */ @SuppressWarnings("unchecked") @Override - public default R query(TemporalQuery query) { - if (query == Queries.chronology()) { - return (R) getChronology(); - } else if (query == Queries.precision()) { + default R query(TemporalQuery query) { + if (query == TemporalQuery.precision()) { return (R) ERAS; } return TemporalAccessor.super.query(query); @@ -339,7 +296,7 @@ * @throws ArithmeticException if numeric overflow occurs */ @Override - public default Temporal adjustInto(Temporal temporal) { + default Temporal adjustInto(Temporal temporal) { return temporal.with(ERA, getValue()); } @@ -359,7 +316,7 @@ * @param locale the locale to use, not null * @return the text value of the era, not null */ - public default String getDisplayName(TextStyle style, Locale locale) { + default String getDisplayName(TextStyle style, Locale locale) { return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).format(this); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/HijrahChronology.java --- a/src/share/classes/java/time/chrono/HijrahChronology.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/HijrahChronology.java Sat Apr 13 21:51:36 2013 +0100 @@ -59,9 +59,13 @@ import static java.time.temporal.ChronoField.EPOCH_DAY; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; -import java.text.ParseException; +import java.security.AccessController; +import java.security.PrivilegedActionException; import java.time.Clock; import java.time.DateTimeException; import java.time.Instant; @@ -73,106 +77,135 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +import sun.util.logging.PlatformLogger; /** - * The Hijrah calendar system. + * The Hijrah calendar is a lunar calendar supporting Islamic calendars. *

    - * This chronology defines the rules of the Hijrah calendar system. + * The HijrahChronology follows the rules of the Hijrah calendar system. The Hijrah + * calendar has several variants based on differences in when the new moon is + * determined to have occurred and where the observation is made. + * In some variants the length of each month is + * computed algorithmically from the astronomical data for the moon and earth and + * in others the length of the month is determined by an authorized sighting + * of the new moon. For the algorithmically based calendars the calendar + * can project into the future. + * For sighting based calendars only historical data from past + * sightings is available. + *

    + * The length of each month is 29 or 30 days. + * Ordinary years have 354 days; leap years have 355 days. + * *

    - * The implementation follows the Freeman-Grenville algorithm (*1) and has following features. - *

      - *
    • A year has 12 months.
    • - *
    • Over a cycle of 30 years there are 11 leap years.
    • - *
    • There are 30 days in month number 1, 3, 5, 7, 9, and 11, - * and 29 days in month number 2, 4, 6, 8, 10, and 12.
    • - *
    • In a leap year month 12 has 30 days.
    • - *
    • In a 30 year cycle, year 2, 5, 7, 10, 13, 16, 18, 21, 24, - * 26, and 29 are leap years.
    • - *
    • Total of 10631 days in a 30 years cycle.
    • - *

    - *

    - * The table shows the features described above. - *

    - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * + * CLDR and LDML identify variants: + *
    Hijrah Calendar Months
    # of monthName of monthNumber of days
    1Muharram30
    2Safar29
    3Rabi'al-Awwal30
    4Rabi'ath-Thani29
    5Jumada l-Ula30
    6Jumada t-Tania29
    7Rajab30
    8Sha`ban29
    9Ramadan30
    10Shawwal29
    11Dhu 'l-Qa`da30
    12Dhu 'l-Hijja29, but 30 days in years 2, 5, 7, 10,
    - * 13, 16, 18, 21, 24, 26, and 29
    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * *
    Chronology IDCalendar TypeLocale extension, see {@link java.util.Locale}Description
    Hijrah-umalquraislamic-umalquraca-islamic-cv-umalquraIslamic - Umm Al-Qura calendar of Saudi Arabia
    - *
    + *

    Additional variants may be available through {@link Chronology#getAvailableChronologies()}. + * + *

    Example

    *

    - * (*1) The algorithm is taken from the book, - * The Muslim and Christian Calendars by G.S.P. Freeman-Grenville. - *

    + * Selecting the chronology from the locale uses {@link Chronology#ofLocale} + * to find the Chronology based on Locale supported BCP 47 extension mechanism + * to request a specific calendar ("ca") and variant ("cv"). For example, + *

    + *
    + *      Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-cv-umalqura");
    + *      Chronology chrono = Chronology.ofLocale(locale);
    + * 
    * *

    Specification for implementors

    * This class is immutable and thread-safe. + *

    Implementation Note for Hijrah Calendar Variant Configuration

    + * Each Hijrah variant is configured individually. Each variant is defined by a + * property resource that defines the {@code ID}, the {@code calendar type}, + * the start of the calendar, the alignment with the + * ISO calendar, and the length of each month for a range of years. + * The variants are identified in the {@code calendars.properties} file. + * The new properties are prefixed with {@code "calendars.hijrah."}: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Property NameProperty valueDescription
    calendars.hijrah.{ID}The property resource defining the {@code {ID}} variantThe property resource is located with the {@code calendars.properties} file
    calendars.hijrah.{ID}.typeThe calendar typeLDML defines the calendar type names
    + *

    + * The Hijrah property resource is a set of properties that describe the calendar. + * The syntax is defined by {@code java.util.Properties#load(Reader)}. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Property Name Property value Description
    idChronology Id, for example, "Hijrah-umalqura"The Id of the calendar in common usage
    typeCalendar type, for example, "islamic-umalqura"LDML defines the calendar types
    versionVersion, for example: "1.8.0_1"The version of the Hijrah variant data
    iso-startISO start date, formatted as {@code yyyy-MM-dd}, for example: "1900-04-30"The ISO date of the first day of the minimum Hijrah year.
    yyyy - a numeric 4 digit year, for example "1434"The value is a sequence of 12 month lengths, + * for example: "29 30 29 30 29 30 30 30 29 30 29 29"The lengths of the 12 months of the year separated by whitespace. + * A numeric year property must be present for every year without any gaps. + * The month lengths must be between 29-32 inclusive. + *
    * * @since 1.8 */ @@ -182,336 +215,161 @@ * The Hijrah Calendar id. */ private final String typeId; - /** * The Hijrah calendarType. */ - private final String calendarType; - - /** - * The singleton instance for the era before the current one - Before Hijrah - - * which has the value 0. - */ - public static final Era ERA_BEFORE_AH = HijrahEra.BEFORE_AH; - /** - * The singleton instance for the current era - Hijrah - which has the value 1. - */ - public static final Era ERA_AH = HijrahEra.AH; + private transient final String calendarType; /** * Serialization version. */ private static final long serialVersionUID = 3127340209035924785L; /** - * The minimum valid year-of-era. - */ - public static final int MIN_YEAR_OF_ERA = 1; - /** - * The maximum valid year-of-era. - * This is currently set to 9999 but may be changed to increase the valid range - * in a future version of the specification. + * Singleton instance of the Islamic Umm Al-Qura calendar of Saudi Arabia. + * Other Hijrah chronology variants may be available from + * {@link Chronology#getAvailableChronologies}. */ - public static final int MAX_YEAR_OF_ERA = 9999; - + public static final HijrahChronology INSTANCE; /** - * Number of Gregorian day of July 19, year 622 (Gregorian), which is epoch day - * of Hijrah calendar. - */ - private static final int HIJRAH_JAN_1_1_GREGORIAN_DAY = -492148; - /** - * 0-based, for number of day-of-year in the beginning of month in normal - * year. + * Array of epoch days indexed by Hijrah Epoch month. + * Computed by {@link #loadCalendarData}. */ - private static final int NUM_DAYS[] = - {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; - /** - * 0-based, for number of day-of-year in the beginning of month in leap year. - */ - private static final int LEAP_NUM_DAYS[] = - {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; + private transient int[] hijrahEpochMonthStartDays; /** - * 0-based, for day-of-month in normal year. + * The minimum epoch day of this Hijrah calendar. + * Computed by {@link #loadCalendarData}. */ - private static final int MONTH_LENGTH[] = - {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29}; + private transient int minEpochDay; /** - * 0-based, for day-of-month in leap year. + * The maximum epoch day for which calendar data is available. + * Computed by {@link #loadCalendarData}. */ - private static final int LEAP_MONTH_LENGTH[] = - {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30}; - + private transient int maxEpochDay; /** - *

    -     *                            Greatest       Least
    -     * Field name        Minimum   Minimum     Maximum     Maximum
    -     * ----------        -------   -------     -------     -------
    -     * ERA                     0         0           1           1
    -     * YEAR_OF_ERA             1         1        9999        9999
    -     * MONTH_OF_YEAR           1         1          12          12
    -     * DAY_OF_MONTH            1         1          29          30
    -     * DAY_OF_YEAR             1         1         354         355
    -     * 
    - * - * Minimum values. + * The minimum epoch month. + * Computed by {@link #loadCalendarData}. + */ + private transient int hijrahStartEpochMonth; + /** + * The minimum length of a month. + * Computed by {@link #createEpochMonths}. + */ + private transient int minMonthLength; + /** + * The maximum length of a month. + * Computed by {@link #createEpochMonths}. */ - private static final int MIN_VALUES[] = - { - 0, - MIN_YEAR_OF_ERA, - 0, - 1, - 0, - 1, - 1 - }; - + private transient int maxMonthLength; + /** + * The minimum length of a year in days. + * Computed by {@link #createEpochMonths}. + */ + private transient int minYearLength; /** - * Least maximum values. + * The maximum length of a year in days. + * Computed by {@link #createEpochMonths}. */ - private static final int LEAST_MAX_VALUES[] = - { - 1, - MAX_YEAR_OF_ERA, - 11, - 51, - 5, - 29, - 354 - }; + private transient int maxYearLength; + /** + * A reference to the properties stored in + * ${java.home}/lib/calendars.properties + */ + private transient final static Properties calendarProperties; /** - * Maximum values. - */ - private static final int MAX_VALUES[] = - { - 1, - MAX_YEAR_OF_ERA, - 11, - 52, - 6, - 30, - 355 - }; - - /** - * Position of day-of-month. This value is used to get the min/max value - * from an array. - */ - private static final int POSITION_DAY_OF_MONTH = 5; - /** - * Position of day-of-year. This value is used to get the min/max value from - * an array. - */ - private static final int POSITION_DAY_OF_YEAR = 6; - /** - * Zero-based start date of cycle year. + * Prefix of property names for Hijrah calendar variants. */ - private static final int CYCLEYEAR_START_DATE[] = - { - 0, - 354, - 709, - 1063, - 1417, - 1772, - 2126, - 2481, - 2835, - 3189, - 3544, - 3898, - 4252, - 4607, - 4961, - 5315, - 5670, - 6024, - 6379, - 6733, - 7087, - 7442, - 7796, - 8150, - 8505, - 8859, - 9214, - 9568, - 9922, - 10277 - }; - - /** - * Holding the adjusted month days in year. The key is a year (Integer) and - * the value is the all the month days in year (int[]). - */ - private final HashMap ADJUSTED_MONTH_DAYS = new HashMap<>(); - /** - * Holding the adjusted month length in year. The key is a year (Integer) - * and the value is the all the month length in year (int[]). - */ - private final HashMap ADJUSTED_MONTH_LENGTHS = new HashMap<>(); + private static final String PROP_PREFIX = "calendar.hijrah."; /** - * Holding the adjusted days in the 30 year cycle. The key is a cycle number - * (Integer) and the value is the all the starting days of the year in the - * cycle (int[]). - */ - private final HashMap ADJUSTED_CYCLE_YEARS = new HashMap<>(); - /** - * Holding the adjusted cycle in the 1 - 30000 year. The key is the cycle - * number (Integer) and the value is the starting days in the cycle in the - * term. - */ - private final long[] ADJUSTED_CYCLES; - /** - * Holding the adjusted min values. - */ - private final int[] ADJUSTED_MIN_VALUES; - /** - * Holding the adjusted max least max values. - */ - private final int[] ADJUSTED_LEAST_MAX_VALUES; - /** - * Holding adjusted max values. - */ - private final int[] ADJUSTED_MAX_VALUES; - /** - * Holding the non-adjusted month days in year for non leap year. - */ - private static final int[] DEFAULT_MONTH_DAYS; - /** - * Holding the non-adjusted month days in year for leap year. - */ - private static final int[] DEFAULT_LEAP_MONTH_DAYS; - /** - * Holding the non-adjusted month length for non leap year. + * Suffix of property names containing the calendar type of a variant. */ - private static final int[] DEFAULT_MONTH_LENGTHS; - /** - * Holding the non-adjusted month length for leap year. - */ - private static final int[] DEFAULT_LEAP_MONTH_LENGTHS; - /** - * Holding the non-adjusted 30 year cycle starting day. - */ - private static final int[] DEFAULT_CYCLE_YEARS; - /** - * number of 30-year cycles to hold the deviation data. - */ - private static final int MAX_ADJUSTED_CYCLE = 334; // to support year 9999 - - - /** - * Narrow names for eras. - */ - private static final HashMap ERA_NARROW_NAMES = new HashMap<>(); - /** - * Short names for eras. - */ - private static final HashMap ERA_SHORT_NAMES = new HashMap<>(); - /** - * Full names for eras. - */ - private static final HashMap ERA_FULL_NAMES = new HashMap<>(); - /** - * Fallback language for the era names. - */ - private static final String FALLBACK_LANGUAGE = "en"; - - /** - * Singleton instance of the Hijrah chronology. - * Must be initialized after the rest of the static initialization. - */ - public static final HijrahChronology INSTANCE; + private static final String PROP_TYPE_SUFFIX = ".type"; /** * Name data. */ static { - ERA_NARROW_NAMES.put(FALLBACK_LANGUAGE, new String[]{"BH", "HE"}); - ERA_SHORT_NAMES.put(FALLBACK_LANGUAGE, new String[]{"B.H.", "H.E."}); - ERA_FULL_NAMES.put(FALLBACK_LANGUAGE, new String[]{"Before Hijrah", "Hijrah Era"}); - - DEFAULT_MONTH_DAYS = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); - - DEFAULT_LEAP_MONTH_DAYS = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); + try { + calendarProperties = sun.util.calendar.BaseCalendar.getCalendarProperties(); + } catch (IOException ioe) { + throw new InternalError("Can't initialize lib/calendars.properties", ioe); + } - DEFAULT_MONTH_LENGTHS = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); - - DEFAULT_LEAP_MONTH_LENGTHS = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); + try { + INSTANCE = new HijrahChronology("Hijrah-umalqura"); + // Register it by its aliases + Chronology.registerChrono(INSTANCE, "Hijrah"); + Chronology.registerChrono(INSTANCE, "islamic"); - DEFAULT_CYCLE_YEARS = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); - - INSTANCE = new HijrahChronology(); + } catch (Exception ex) { + // Absence of Hijrah calendar is fatal to initializing this class. + PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); + logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex); + throw new RuntimeException("Unable to initialize Hijrah-umalqura calendar", ex.getCause()); + } + registerVariants(); + } - String extraCalendars = java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction("java.time.chrono.HijrahCalendars")); - if (extraCalendars != null) { - try { - // Split on whitespace - String[] splits = extraCalendars.split("\\s"); - for (String cal : splits) { - if (!cal.isEmpty()) { - // Split on the delimiter between typeId "-" calendarType - String[] type = cal.split("-"); - Chronology cal2 = new HijrahChronology(type[0], type.length > 1 ? type[1] : type[0]); - } + /** + * For each Hijrah variant listed, create the HijrahChronology and register it. + * Exceptions during initialization are logged but otherwise ignored. + */ + private static void registerVariants() { + for (String name : calendarProperties.stringPropertyNames()) { + if (name.startsWith(PROP_PREFIX)) { + String id = name.substring(PROP_PREFIX.length()); + if (id.indexOf('.') >= 0) { + continue; // no name or not a simple name of a calendar } - } catch (Exception ex) { - // Log the error - // ex.printStackTrace(); + if (id.equals(INSTANCE.getId())) { + continue; // do not duplicate the default + } + try { + // Create and register the variant + HijrahChronology chrono = new HijrahChronology(id); + Chronology.registerChrono(chrono); + } catch (Exception ex) { + // Log error and continue + PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); + logger.severe("Unable to initialize Hijrah calendar: " + id, ex); + } } } } /** - * Restricted constructor. - */ - private HijrahChronology() { - this("Hijrah", "islamicc"); - } - /** - * Constructor for name and type HijrahChronology. + * Create a HijrahChronology for the named variant. + * The resource and calendar type are retrieved from properties + * in the {@code calendars.properties}. + * The property names are {@code "calendar.hijrah." + id} + * and {@code "calendar.hijrah." + id + ".type"} * @param id the id of the calendar - * @param calendarType the calendar type + * @throws Exception if the resource can not be accessed or + * the format is invalid */ - private HijrahChronology(String id, String calendarType) { + private HijrahChronology(String id) throws Exception { + if (id.isEmpty()) { + throw new IllegalArgumentException("calendar id is empty"); + } this.typeId = id; - this.calendarType = calendarType; - - ADJUSTED_CYCLES = new long[MAX_ADJUSTED_CYCLE]; - for (int i = 0; i < ADJUSTED_CYCLES.length; i++) { - ADJUSTED_CYCLES[i] = (10631L * i); - } - // Initialize min values, least max values and max values. - ADJUSTED_MIN_VALUES = Arrays.copyOf(MIN_VALUES, MIN_VALUES.length); - ADJUSTED_LEAST_MAX_VALUES = Arrays.copyOf(LEAST_MAX_VALUES, LEAST_MAX_VALUES.length); - ADJUSTED_MAX_VALUES = Arrays.copyOf(MAX_VALUES,MAX_VALUES.length); + this.calendarType = calendarProperties.getProperty(PROP_PREFIX + id + PROP_TYPE_SUFFIX); try { - // Implicitly reads deviation data for this HijrahChronology. - boolean any = HijrahDeviationReader.readDeviation(typeId, calendarType, this::addDeviationAsHijrah); - } catch (IOException | ParseException e) { - // do nothing. Log deviation config errors. - //e.printStackTrace(); + String resource = calendarProperties.getProperty(PROP_PREFIX + id); + Objects.requireNonNull(resource, "Resource missing for calendar"); + loadCalendarData(resource); + } catch (Exception ex) { + throw new Exception("Unable to initialize HijrahCalendar: " + id, ex); } } - /** - * Resolve singleton. - * - * @return the singleton instance, not null - */ - private Object readResolve() { - return INSTANCE; - } - //----------------------------------------------------------------------- /** - * Gets the ID of the chronology - 'Hijrah'. + * Gets the ID of the chronology. *

    - * The ID uniquely identifies the {@code Chronology}. - * It can be used to lookup the {@code Chronology} using {@link #of(String)}. + * The ID uniquely identifies the {@code Chronology}. It can be used to + * lookup the {@code Chronology} using {@link #of(String)}. * - * @return the chronology ID - 'Hijrah' + * @return the chronology ID, non-null * @see #getCalendarType() */ @Override @@ -520,15 +378,14 @@ } /** - * Gets the calendar type of the underlying calendar system - 'islamicc'. + * Gets the calendar type of the Islamic calendar. *

    * The calendar type is an identifier defined by the * Unicode Locale Data Markup Language (LDML) specification. * It can be used to lookup the {@code Chronology} using {@link #of(String)}. - * It can also be used as part of a locale, accessible via - * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. * - * @return the calendar system type - 'islamicc' + * @return the calendar system type; non-null if the calendar has + * a standard type, otherwise null * @see #getId() */ @Override @@ -537,33 +394,78 @@ } //----------------------------------------------------------------------- + /** + * Obtains a local date in Hijrah calendar system from the + * era, year-of-era, month-of-year and day-of-month fields. + * + * @param era the Hijrah era, not null + * @param yearOfEra the year-of-era + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Hijrah local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code HijrahEra} + */ + @Override + public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + return date(prolepticYear(era, yearOfEra), month, dayOfMonth); + } + + /** + * Obtains a local date in Hijrah calendar system from the + * proleptic-year, month-of-year and day-of-month fields. + * + * @param prolepticYear the proleptic-year + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Hijrah local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public HijrahDate date(int prolepticYear, int month, int dayOfMonth) { return HijrahDate.of(this, prolepticYear, month, dayOfMonth); } + /** + * Obtains a local date in Hijrah calendar system from the + * era, year-of-era and day-of-year fields. + * + * @param era the Hijrah era, not null + * @param yearOfEra the year-of-era + * @param dayOfYear the day-of-year + * @return the Hijrah local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code HijrahEra} + */ + @Override + public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + } + + /** + * Obtains a local date in Hijrah calendar system from the + * proleptic-year and day-of-year fields. + * + * @param prolepticYear the proleptic-year + * @param dayOfYear the day-of-year + * @return the Hijrah local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) { return HijrahDate.of(this, prolepticYear, 1, 1).plusDays(dayOfYear - 1); // TODO better } - @Override - public HijrahDate date(TemporalAccessor temporal) { - if (temporal instanceof HijrahDate) { - return (HijrahDate) temporal; - } - return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY)); - } - - @Override - public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) { - return date(prolepticYear(era, yearOfEra), month, dayOfMonth); - - } - - @Override - public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { - return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + /** + * Obtains a local date in the Hijrah calendar system from the epoch-day. + * + * @param epochDay the epoch day + * @return the Hijrah local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public HijrahDate dateEpochDay(long epochDay) { + return HijrahDate.ofEpochDay(this, epochDay); } @Override @@ -582,47 +484,50 @@ } @Override + public HijrahDate date(TemporalAccessor temporal) { + if (temporal instanceof HijrahDate) { + return (HijrahDate) temporal; + } + return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY)); + } + + @Override public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { - return (ChronoLocalDateTime)super.localDateTime(temporal); + return (ChronoLocalDateTime) super.localDateTime(temporal); } @Override public ChronoZonedDateTime zonedDateTime(TemporalAccessor temporal) { - return (ChronoZonedDateTime)super.zonedDateTime(temporal); + return (ChronoZonedDateTime) super.zonedDateTime(temporal); } @Override public ChronoZonedDateTime zonedDateTime(Instant instant, ZoneId zone) { - return (ChronoZonedDateTime)super.zonedDateTime(instant, zone); + return (ChronoZonedDateTime) super.zonedDateTime(instant, zone); } //----------------------------------------------------------------------- @Override public boolean isLeapYear(long prolepticYear) { - return isLeapYear0(prolepticYear); - } - /** - * Returns if the year is a leap year. - * @param prolepticYear he year to compute from - * @return {@code true} if the year is a leap year, otherwise {@code false} - */ - private static boolean isLeapYear0(long prolepticYear) { - return (14 + 11 * (prolepticYear > 0 ? prolepticYear : -prolepticYear)) % 30 < 11; + int epochMonth = yearToEpochMonth((int) prolepticYear); + if (epochMonth < 0 || epochMonth > maxEpochDay) { + throw new DateTimeException("Hijrah date out of range"); + } + int len = getYearLength((int) prolepticYear); + return (len > 354); } @Override public int prolepticYear(Era era, int yearOfEra) { if (era instanceof HijrahEra == false) { - throw new DateTimeException("Era must be HijrahEra"); + throw new ClassCastException("Era must be HijrahEra"); } - return (era == HijrahEra.AH ? yearOfEra : 1 - yearOfEra); + return yearOfEra; } @Override public Era eraOf(int eraValue) { switch (eraValue) { - case 0: - return HijrahEra.BEFORE_AH; case 1: return HijrahEra.AH; default: @@ -638,430 +543,159 @@ //----------------------------------------------------------------------- @Override public ValueRange range(ChronoField field) { + if (field instanceof ChronoField) { + ChronoField f = field; + switch (f) { + case DAY_OF_MONTH: + return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength()); + case DAY_OF_YEAR: + return ValueRange.of(1, getMaximumDayOfYear()); + case ALIGNED_WEEK_OF_MONTH: + return ValueRange.of(1, 5); + case YEAR: + case YEAR_OF_ERA: + return ValueRange.of(getMinimumYear(), getMaximumYear()); + default: + return field.range(); + } + } return field.range(); } /** - * Check the validity of a yearOfEra. - * @param yearOfEra the year to check + * Check the validity of a year. + * + * @param prolepticYear the year to check */ - void checkValidYearOfEra(int yearOfEra) { - if (yearOfEra < MIN_YEAR_OF_ERA || - yearOfEra > MAX_YEAR_OF_ERA) { - throw new DateTimeException("Invalid year of Hijrah Era"); - } + int checkValidYear(long prolepticYear) { + if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) { + throw new DateTimeException("Invalid Hijrah year: " + prolepticYear); + } + return (int) prolepticYear; } void checkValidDayOfYear(int dayOfYear) { - if (dayOfYear < 1 || - dayOfYear > getMaximumDayOfYear()) { - throw new DateTimeException("Invalid day of year of Hijrah date"); - } + if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) { + throw new DateTimeException("Invalid Hijrah day of year: " + dayOfYear); + } } void checkValidMonth(int month) { - if (month < 1 || month > 12) { - throw new DateTimeException("Invalid month of Hijrah date"); - } - } - - void checkValidDayOfMonth(int dayOfMonth) { - if (dayOfMonth < 1 || - dayOfMonth > getMaximumDayOfMonth()) { - throw new DateTimeException("Invalid day of month of Hijrah date, day " - + dayOfMonth + " greater than " + getMaximumDayOfMonth() + " or less than 1"); - } + if (month < 1 || month > 12) { + throw new DateTimeException("Invalid Hijrah month: " + month); + } } //----------------------------------------------------------------------- /** - * Returns the int array containing the following field from the julian day. - * - * int[0] = ERA - * int[1] = YEAR - * int[2] = MONTH - * int[3] = DATE - * int[4] = DAY_OF_YEAR - * int[5] = DAY_OF_WEEK + * Returns an array containing the Hijrah year, month and day + * computed from the epoch day. * - * @param gregorianDays a julian day. + * @param epochDay the EpochDay + * @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE */ - int[] getHijrahDateInfo(long gregorianDays) { - int era, year, month, date, dayOfWeek, dayOfYear; - - int cycleNumber, yearInCycle, dayOfCycle; - - long epochDay = gregorianDays - HIJRAH_JAN_1_1_GREGORIAN_DAY; + int[] getHijrahDateInfo(int epochDay) { + if (epochDay < minEpochDay || epochDay >= maxEpochDay) { + throw new DateTimeException("Hijrah date out of range"); + } - if (epochDay >= 0) { - cycleNumber = getCycleNumber(epochDay); // 0 - 99. - dayOfCycle = getDayOfCycle(epochDay, cycleNumber); // 0 - 10631. - yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. - dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); - // 0 - 354/355 - year = cycleNumber * 30 + yearInCycle + 1; // 1-based year. - month = getMonthOfYear(dayOfYear, year); // 0-based month-of-year - date = getDayOfMonth(dayOfYear, month, year); // 0-based date - ++date; // Convert from 0-based to 1-based - era = HijrahEra.AH.getValue(); - } else { - cycleNumber = (int) epochDay / 10631; // 0 or negative number. - dayOfCycle = (int) epochDay % 10631; // -10630 - 0. - if (dayOfCycle == 0) { - dayOfCycle = -10631; - cycleNumber++; - } - yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. - dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); - year = cycleNumber * 30 - yearInCycle; // negative number. - year = 1 - year; - dayOfYear = (isLeapYear(year) ? (dayOfYear + 355) - : (dayOfYear + 354)); - month = getMonthOfYear(dayOfYear, year); - date = getDayOfMonth(dayOfYear, month, year); - ++date; // Convert from 0-based to 1-based - era = HijrahEra.BEFORE_AH.getValue(); - } - // Hijrah day zero is a Friday - dayOfWeek = (int) ((epochDay + 5) % 7); - dayOfWeek += (dayOfWeek <= 0) ? 7 : 0; + int epochMonth = epochDayToEpochMonth(epochDay); + int year = epochMonthToYear(epochMonth); + int month = epochMonthToMonth(epochMonth); + int day1 = epochMonthToEpochDay(epochMonth); + int date = epochDay - day1; // epochDay - dayOfEpoch(year, month); - int dateInfo[] = new int[6]; - dateInfo[0] = era; - dateInfo[1] = year; - dateInfo[2] = month + 1; // change to 1-based. - dateInfo[3] = date; - dateInfo[4] = dayOfYear + 1; // change to 1-based. - dateInfo[5] = dayOfWeek; + int dateInfo[] = new int[3]; + dateInfo[0] = year; + dateInfo[1] = month + 1; // change to 1-based. + dateInfo[2] = date + 1; // change to 1-based. return dateInfo; } /** - * Return Gregorian epoch day from Hijrah year, month, and day. + * Return the epoch day computed from Hijrah year, month, and day. * - * @param prolepticYear the year to represent, caller calculated - * @param monthOfYear the month-of-year to represent, caller calculated - * @param dayOfMonth the day-of-month to represent, caller calculated - * @return a julian day - */ - long getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { - long day = yearToGregorianEpochDay(prolepticYear); - day += getMonthDays(monthOfYear - 1, prolepticYear); - day += dayOfMonth; - return day; - } - - /** - * Returns the Gregorian epoch day from the proleptic year - * @param prolepticYear the proleptic year - * @return the Epoch day + * @param prolepticYear the year to represent, 0-origin + * @param monthOfYear the month-of-year to represent, 1-origin + * @param dayOfMonth the day-of-month to represent, 1-origin + * @return the epoch day */ - private long yearToGregorianEpochDay(int prolepticYear) { - - int cycleNumber = (prolepticYear - 1) / 30; // 0-based. - int yearInCycle = (prolepticYear - 1) % 30; // 0-based. - - int dayInCycle = getAdjustedCycle(cycleNumber)[Math.abs(yearInCycle)] - ; - - if (yearInCycle < 0) { - dayInCycle = -dayInCycle; - } - - Long cycleDays; - - try { - cycleDays = ADJUSTED_CYCLES[cycleNumber]; - } catch (ArrayIndexOutOfBoundsException e) { - cycleDays = null; - } - - if (cycleDays == null) { - cycleDays = new Long(cycleNumber * 10631); - } - - return (cycleDays.longValue() + dayInCycle + HIJRAH_JAN_1_1_GREGORIAN_DAY - 1); - } - - /** - * Returns the 30 year cycle number from the epoch day. - * - * @param epochDay an epoch day - * @return a cycle number - */ - private int getCycleNumber(long epochDay) { - long[] days = ADJUSTED_CYCLES; - int cycleNumber; - try { - for (int i = 0; i < days.length; i++) { - if (epochDay < days[i]) { - return i - 1; - } - } - cycleNumber = (int) epochDay / 10631; - } catch (ArrayIndexOutOfBoundsException e) { - cycleNumber = (int) epochDay / 10631; + long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { + checkValidMonth(monthOfYear); + int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1); + if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) { + throw new DateTimeException("Invalid Hijrah date, year: " + + prolepticYear + ", month: " + monthOfYear); } - return cycleNumber; - } - - /** - * Returns day of cycle from the epoch day and cycle number. - * - * @param epochDay an epoch day - * @param cycleNumber a cycle number - * @return a day of cycle - */ - private int getDayOfCycle(long epochDay, int cycleNumber) { - Long day; - - try { - day = ADJUSTED_CYCLES[cycleNumber]; - } catch (ArrayIndexOutOfBoundsException e) { - day = null; - } - if (day == null) { - day = new Long(cycleNumber * 10631); - } - return (int) (epochDay - day.longValue()); - } - - /** - * Returns the year in cycle from the cycle number and day of cycle. - * - * @param cycleNumber a cycle number - * @param dayOfCycle day of cycle - * @return a year in cycle - */ - private int getYearInCycle(int cycleNumber, long dayOfCycle) { - int[] cycles = getAdjustedCycle(cycleNumber); - if (dayOfCycle == 0) { - return 0; + if (dayOfMonth < 1 || dayOfMonth > getMonthLength(prolepticYear, monthOfYear)) { + throw new DateTimeException("Invalid Hijrah day of month: " + dayOfMonth); } - - if (dayOfCycle > 0) { - for (int i = 0; i < cycles.length; i++) { - if (dayOfCycle < cycles[i]) { - return i - 1; - } - } - return 29; - } else { - dayOfCycle = -dayOfCycle; - for (int i = 0; i < cycles.length; i++) { - if (dayOfCycle <= cycles[i]) { - return i - 1; - } - } - return 29; - } - } - - /** - * Returns adjusted 30 year cycle starting day as Integer array from the - * cycle number specified. - * - * @param cycleNumber a cycle number - * @return an Integer array - */ - int[] getAdjustedCycle(int cycleNumber) { - int[] cycles; - try { - cycles = ADJUSTED_CYCLE_YEARS.get(cycleNumber); - } catch (ArrayIndexOutOfBoundsException e) { - cycles = null; - } - if (cycles == null) { - cycles = DEFAULT_CYCLE_YEARS; - } - return cycles; + return epochMonthToEpochDay(epochMonth) + (dayOfMonth - 1); } /** - * Returns adjusted month days as Integer array form the year specified. + * Returns day of year for the year and month. * - * @param year a year - * @return an Integer array - */ - int[] getAdjustedMonthDays(int year) { - int[] newMonths; - try { - newMonths = ADJUSTED_MONTH_DAYS.get(year); - } catch (ArrayIndexOutOfBoundsException e) { - newMonths = null; - } - if (newMonths == null) { - if (isLeapYear0(year)) { - newMonths = DEFAULT_LEAP_MONTH_DAYS; - } else { - newMonths = DEFAULT_MONTH_DAYS; - } - } - return newMonths; - } - - /** - * Returns adjusted month length as Integer array form the year specified. - * - * @param year a year - * @return an Integer array + * @param prolepticYear a proleptic year + * @param month a month, 1-origin + * @return the day of year, 1-origin */ - int[] getAdjustedMonthLength(int year) { - int[] newMonths; - try { - newMonths = ADJUSTED_MONTH_LENGTHS.get(year); - } catch (ArrayIndexOutOfBoundsException e) { - newMonths = null; - } - if (newMonths == null) { - if (isLeapYear0(year)) { - newMonths = DEFAULT_LEAP_MONTH_LENGTHS; - } else { - newMonths = DEFAULT_MONTH_LENGTHS; - } - } - return newMonths; - } - - /** - * Returns day-of-year. - * - * @param cycleNumber a cycle number - * @param dayOfCycle day of cycle - * @param yearInCycle year in cycle - * @return day-of-year - */ - private int getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle) { - int[] cycles = getAdjustedCycle(cycleNumber); - - if (dayOfCycle > 0) { - return dayOfCycle - cycles[yearInCycle]; - } else { - return cycles[yearInCycle] + dayOfCycle; - } + int getDayOfYear(int prolepticYear, int month) { + return yearMonthToDayOfYear(prolepticYear, (month - 1)); } /** - * Returns month-of-year. 0-based. + * Returns month length for the year and month. * - * @param dayOfYear day-of-year - * @param year a year - * @return month-of-year - */ - private int getMonthOfYear(int dayOfYear, int year) { - - int[] newMonths = getAdjustedMonthDays(year); - - if (dayOfYear >= 0) { - for (int i = 0; i < newMonths.length; i++) { - if (dayOfYear < newMonths[i]) { - return i - 1; - } - } - return 11; - } else { - dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355) - : (dayOfYear + 354)); - for (int i = 0; i < newMonths.length; i++) { - if (dayOfYear < newMonths[i]) { - return i - 1; - } - } - return 11; - } - } - - /** - * Returns day-of-month. - * - * @param dayOfYear day of year - * @param month month - * @param year year - * @return day-of-month + * @param prolepticYear a proleptic year + * @param monthOfYear a month, 1-origin. + * @return the length of the month */ - private int getDayOfMonth(int dayOfYear, int month, int year) { - - int[] newMonths = getAdjustedMonthDays(year); - - if (dayOfYear >= 0) { - if (month > 0) { - return dayOfYear - newMonths[month]; - } else { - return dayOfYear; - } - } else { - dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355) - : (dayOfYear + 354)); - if (month > 0) { - return dayOfYear - newMonths[month]; - } else { - return dayOfYear; - } + int getMonthLength(int prolepticYear, int monthOfYear) { + int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1); + if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) { + throw new DateTimeException("Invalid Hijrah date, year: " + + prolepticYear + ", month: " + monthOfYear); } - } - - - /** - * Returns month days from the beginning of year. - * - * @param month month (0-based) - * @parma year year - * @return month days from the beginning of year - */ - private int getMonthDays(int month, int year) { - int[] newMonths = getAdjustedMonthDays(year); - return newMonths[month]; - } - - /** - * Returns month length. - * - * @param month month (0-based) - * @param year year - * @return month length - */ - private int getMonthLength(int month, int year) { - int[] newMonths = getAdjustedMonthLength(year); - return newMonths[month]; + return epochMonthLength(epochMonth); } /** * Returns year length. + * Note: The 12th month must exist in the data. * - * @param year year - * @return year length + * @param prolepticYear a proleptic year + * @return year length in days */ - int getYearLength(int year) { - - int cycleNumber = (year - 1) / 30; - int[] cycleYears; - try { - cycleYears = ADJUSTED_CYCLE_YEARS.get(cycleNumber); - } catch (ArrayIndexOutOfBoundsException e) { - cycleYears = null; - } - if (cycleYears != null) { - int yearInCycle = (year - 1) % 30; - if (yearInCycle == 29) { - return (int)(ADJUSTED_CYCLES[cycleNumber + 1] - - ADJUSTED_CYCLES[cycleNumber] - - cycleYears[yearInCycle]); - } - return cycleYears[yearInCycle + 1] - - cycleYears[yearInCycle]; - } else { - return isLeapYear0(year) ? 355 : 354; - } + int getYearLength(int prolepticYear) { + return yearMonthToDayOfYear(prolepticYear, 12); } + /** + * Return the minimum supported Hijrah year. + * + * @return the minimum + */ + int getMinimumYear() { + return epochMonthToYear(0); + } + + /** + * Return the maximum supported Hijrah ear. + * + * @return the minimum + */ + int getMaximumYear() { + return epochMonthToYear(hijrahEpochMonthStartDays.length - 1) - 1; + } /** * Returns maximum day-of-month. * * @return maximum day-of-month */ - int getMaximumDayOfMonth() { - return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]; + int getMaximumMonthLength() { + return maxMonthLength; } /** @@ -1069,8 +703,8 @@ * * @return smallest maximum day-of-month */ - int getSmallestMaximumDayOfMonth() { - return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]; + int getMinimumMonthLength() { + return minMonthLength; } /** @@ -1079,7 +713,7 @@ * @return maximum day-of-year */ int getMaximumDayOfYear() { - return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR]; + return maxYearLength; } /** @@ -1088,296 +722,303 @@ * @return smallest maximum day-of-year */ int getSmallestMaximumDayOfYear() { - return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]; + return minYearLength; + } + + /** + * Returns the epochMonth found by locating the epochDay in the table. The + * epochMonth is the index in the table + * + * @param epochDay + * @return The index of the element of the start of the month containing the + * epochDay. + */ + private int epochDayToEpochMonth(int epochDay) { + // binary search + int ndx = Arrays.binarySearch(hijrahEpochMonthStartDays, epochDay); + if (ndx < 0) { + ndx = -ndx - 2; + } + return ndx; + } + + /** + * Returns the year computed from the epochMonth + * + * @param epochMonth the epochMonth + * @return the Hijrah Year + */ + private int epochMonthToYear(int epochMonth) { + return (epochMonth + hijrahStartEpochMonth) / 12; } - // ----- Deviation handling -----// + /** + * Returns the epochMonth for the Hijrah Year. + * + * @param year the HijrahYear + * @return the epochMonth for the beginning of the year. + */ + private int yearToEpochMonth(int year) { + return (year * 12) - hijrahStartEpochMonth; + } + + /** + * Returns the Hijrah month from the epochMonth. + * + * @param epochMonth the epochMonth + * @return the month of the Hijrah Year + */ + private int epochMonthToMonth(int epochMonth) { + return (epochMonth + hijrahStartEpochMonth) % 12; + } + + /** + * Returns the epochDay for the start of the epochMonth. + * + * @param epochMonth the epochMonth + * @return the epochDay for the start of the epochMonth. + */ + private int epochMonthToEpochDay(int epochMonth) { + return hijrahEpochMonthStartDays[epochMonth]; + + } + + /** + * Returns the day of year for the requested HijrahYear and month. + * + * @param prolepticYear the Hijrah year + * @param month the Hijrah month + * @return the day of year for the start of the month of the year + */ + private int yearMonthToDayOfYear(int prolepticYear, int month) { + int epochMonthFirst = yearToEpochMonth(prolepticYear); + return epochMonthToEpochDay(epochMonthFirst + month) + - epochMonthToEpochDay(epochMonthFirst); + } /** - * Adds deviation definition. The year and month specified should be the - * calculated Hijrah year and month. The month is 1 based. e.g. 9 for - * Ramadan (9th month) Addition of anything minus deviation days is - * calculated negatively in the case the user wants to subtract days from - * the calendar. For example, adding -1 days will subtract one day from the - * current date. + * Returns the length of the epochMonth. It is computed from the start of + * the following month minus the start of the requested month. * - * @param startYear start year, 1 origin - * @param startMonth start month, 1 origin - * @param endYear end year, 1 origin - * @param endMonth end month, 1 origin - * @param offset offset -2, -1, +1, +2 + * @param epochMonth the epochMonth; assumed to be within range + * @return the length in days of the epochMonth */ - private void addDeviationAsHijrah(Deviation entry) { - int startYear = entry.startYear; - int startMonth = entry.startMonth - 1 ; - int endYear = entry.endYear; - int endMonth = entry.endMonth - 1; - int offset = entry.offset; + private int epochMonthLength(int epochMonth) { + // The very last entry in the epochMonth table is not the start of a month + return hijrahEpochMonthStartDays[epochMonth + 1] + - hijrahEpochMonthStartDays[epochMonth]; + } + + //----------------------------------------------------------------------- + private static final String KEY_ID = "id"; + private static final String KEY_TYPE = "type"; + private static final String KEY_VERSION = "version"; + private static final String KEY_ISO_START = "iso-start"; - if (startYear < 1) { - throw new IllegalArgumentException("startYear < 1"); - } - if (endYear < 1) { - throw new IllegalArgumentException("endYear < 1"); - } - if (startMonth < 0 || startMonth > 11) { - throw new IllegalArgumentException( - "startMonth < 0 || startMonth > 11"); + /** + * Return the configuration properties from the resource. + *

    + * The default location of the variant configuration resource is: + *

    +     *   "$java.home/lib/" + resource-name
    +     * 
    + * + * @param resource the name of the calendar property resource + * @return a Properties containing the properties read from the resource. + * @throws Exception if access to the property resource fails + */ + private static Properties readConfigProperties(final String resource) throws Exception { + try { + return AccessController + .doPrivileged((java.security.PrivilegedExceptionAction) + () -> { + String libDir = System.getProperty("java.home") + File.separator + "lib"; + File file = new File(libDir, resource); + Properties props = new Properties(); + try (InputStream is = new FileInputStream(file)) { + props.load(is); + } + return props; + }); + } catch (PrivilegedActionException pax) { + throw pax.getException(); } - if (endMonth < 0 || endMonth > 11) { - throw new IllegalArgumentException("endMonth < 0 || endMonth > 11"); - } - if (endYear > 9999) { - throw new IllegalArgumentException("endYear > 9999"); - } - if (endYear < startYear) { - throw new IllegalArgumentException("startYear > endYear"); - } - if (endYear == startYear && endMonth < startMonth) { - throw new IllegalArgumentException( - "startYear == endYear && endMonth < startMonth"); - } + } - // Adjusting start year. - boolean isStartYLeap = isLeapYear0(startYear); - - // Adjusting the number of month. - int[] orgStartMonthNums = ADJUSTED_MONTH_DAYS.get(startYear); - if (orgStartMonthNums == null) { - if (isStartYLeap) { - orgStartMonthNums = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); - } else { - orgStartMonthNums = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); - } - } - - int[] newStartMonthNums = new int[orgStartMonthNums.length]; + /** + * Loads and processes the Hijrah calendar properties file. + * The starting Hijrah date and the corresponding ISO date are + * extracted and used to calculate the epochDate offset. + * The version number is identified and ignored. + * Everything else is the data for a year with containing the length of each + * of 12 months. + * + * @param resourceName containing the properties defining the calendar, not null + * @throws IllegalArgumentException if any of the values are malformed + * @throws NumberFormatException if numbers, including properties that should + * be years are invalid + * @throws IOException if access to the property resource fails. + */ + private void loadCalendarData(String resourceName) throws Exception { + Properties props = readConfigProperties(resourceName); - for (int month = 0; month < 12; month++) { - if (month > startMonth) { - newStartMonthNums[month] = (orgStartMonthNums[month] - offset); - } else { - newStartMonthNums[month] = (orgStartMonthNums[month]); - } - } - - ADJUSTED_MONTH_DAYS.put(startYear, newStartMonthNums); - - // Adjusting the days of month. - - int[] orgStartMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear); - if (orgStartMonthLengths == null) { - if (isStartYLeap) { - orgStartMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); - } else { - orgStartMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); - } - } - - int[] newStartMonthLengths = new int[orgStartMonthLengths.length]; - - for (int month = 0; month < 12; month++) { - if (month == startMonth) { - newStartMonthLengths[month] = orgStartMonthLengths[month] - offset; - } else { - newStartMonthLengths[month] = orgStartMonthLengths[month]; + Map years = new HashMap<>(); + int minYear = Integer.MAX_VALUE; + int maxYear = Integer.MIN_VALUE; + String id = null; + String type = null; + String version = null; + int isoStart = 0; + for (Map.Entry entry : props.entrySet()) { + String key = (String) entry.getKey(); + switch (key) { + case KEY_ID: + id = (String)entry.getValue(); + break; + case KEY_TYPE: + type = (String)entry.getValue(); + break; + case KEY_VERSION: + version = (String)entry.getValue(); + break; + case KEY_ISO_START: { + int[] ymd = parseYMD((String) entry.getValue()); + isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay(); + break; + } + default: + try { + // Everything else is either a year or invalid + int year = Integer.valueOf(key); + int[] months = parseMonths((String) entry.getValue()); + years.put(year, months); + maxYear = Math.max(maxYear, year); + minYear = Math.min(minYear, year); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("bad key: " + key); + } } } - ADJUSTED_MONTH_LENGTHS.put(startYear, newStartMonthLengths); - - if (startYear != endYear) { - // System.out.println("over year"); - // Adjusting starting 30 year cycle. - int sCycleNumber = (startYear - 1) / 30; - int sYearInCycle = (startYear - 1) % 30; // 0-based. - int[] startCycles = ADJUSTED_CYCLE_YEARS.get(sCycleNumber); - if (startCycles == null) { - startCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); - } - - for (int j = sYearInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { - startCycles[j] = startCycles[j] - offset; - } - - // System.out.println(sCycleNumber + ":" + sYearInCycle); - ADJUSTED_CYCLE_YEARS.put(sCycleNumber, startCycles); - - int sYearInMaxY = (startYear - 1) / 30; - int sEndInMaxY = (endYear - 1) / 30; - - if (sYearInMaxY != sEndInMaxY) { - // System.out.println("over 30"); - // Adjusting starting 30 * MAX_ADJUSTED_CYCLE year cycle. - // System.out.println(sYearInMaxY); - - for (int j = sYearInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { - ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] - offset; - } - - // Adjusting ending 30 * MAX_ADJUSTED_CYCLE year cycles. - for (int j = sEndInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { - ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] + offset; - } - } - - // Adjusting ending 30 year cycle. - int eCycleNumber = (endYear - 1) / 30; - int sEndInCycle = (endYear - 1) % 30; // 0-based. - int[] endCycles = ADJUSTED_CYCLE_YEARS.get(eCycleNumber); - if (endCycles == null) { - endCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); - } - for (int j = sEndInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { - endCycles[j] = endCycles[j] + offset; - } - ADJUSTED_CYCLE_YEARS.put(eCycleNumber, endCycles); + if (!getId().equals(id)) { + throw new IllegalArgumentException("Configuration is for a different calendar: " + id); + } + if (!getCalendarType().equals(type)) { + throw new IllegalArgumentException("Configuration is for a different calendar type: " + type); + } + if (version == null || version.isEmpty()) { + throw new IllegalArgumentException("Configuration does not contain a version"); + } + if (isoStart == 0) { + throw new IllegalArgumentException("Configuration does not contain a ISO start date"); } - // Adjusting ending year. - boolean isEndYLeap = isLeapYear0(endYear); - - int[] orgEndMonthDays = ADJUSTED_MONTH_DAYS.get(endYear); + // Now create and validate the array of epochDays indexed by epochMonth + hijrahStartEpochMonth = minYear * 12; + minEpochDay = isoStart; + hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years); + maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1]; - if (orgEndMonthDays == null) { - if (isEndYLeap) { - orgEndMonthDays = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); - } else { - orgEndMonthDays = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); - } - } - - int[] newEndMonthDays = new int[orgEndMonthDays.length]; - - for (int month = 0; month < 12; month++) { - if (month > endMonth) { - newEndMonthDays[month] = orgEndMonthDays[month] + offset; - } else { - newEndMonthDays[month] = orgEndMonthDays[month]; - } + // Compute the min and max year length in days. + for (int year = minYear; year < maxYear; year++) { + int length = getYearLength(year); + minYearLength = Math.min(minYearLength, length); + maxYearLength = Math.max(maxYearLength, length); } - - ADJUSTED_MONTH_DAYS.put(endYear, newEndMonthDays); + } - // Adjusting the days of month. - int[] orgEndMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear); + /** + * Converts the map of year to month lengths ranging from minYear to maxYear + * into a linear contiguous array of epochDays. The index is the hijrahMonth + * computed from year and month and offset by minYear. The value of each + * entry is the epochDay corresponding to the first day of the month. + * + * @param minYear The minimum year for which data is provided + * @param maxYear The maximum year for which data is provided + * @param years a Map of year to the array of 12 month lengths + * @return array of epochDays for each month from min to max + */ + private int[] createEpochMonths(int epochDay, int minYear, int maxYear, Map years) { + // Compute the size for the array of dates + int numMonths = (maxYear - minYear + 1) * 12 + 1; - if (orgEndMonthLengths == null) { - if (isEndYLeap) { - orgEndMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); - } else { - orgEndMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); - } - } + // Initialize the running epochDay as the corresponding ISO Epoch day + int epochMonth = 0; // index into array of epochMonths + int[] epochMonths = new int[numMonths]; + minMonthLength = Integer.MAX_VALUE; + maxMonthLength = Integer.MIN_VALUE; - int[] newEndMonthLengths = new int[orgEndMonthLengths.length]; + // Only whole years are valid, any zero's in the array are illegal + for (int year = minYear; year <= maxYear; year++) { + int[] months = years.get(year);// must not be gaps + for (int month = 0; month < 12; month++) { + int length = months[month]; + epochMonths[epochMonth++] = epochDay; - for (int month = 0; month < 12; month++) { - if (month == endMonth) { - newEndMonthLengths[month] = orgEndMonthLengths[month] + offset; - } else { - newEndMonthLengths[month] = orgEndMonthLengths[month]; + if (length < 29 || length > 32) { + throw new IllegalArgumentException("Invalid month length in year: " + minYear); + } + epochDay += length; + minMonthLength = Math.min(minMonthLength, length); + maxMonthLength = Math.max(maxMonthLength, length); } } - ADJUSTED_MONTH_LENGTHS.put(endYear, newEndMonthLengths); - - int[] startMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear); - int[] endMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear); - int[] startMonthDays = ADJUSTED_MONTH_DAYS.get(startYear); - int[] endMonthDays = ADJUSTED_MONTH_DAYS.get(endYear); - - int startMonthLength = startMonthLengths[startMonth]; - int endMonthLength = endMonthLengths[endMonth]; - int startMonthDay = startMonthDays[11] + startMonthLengths[11]; - int endMonthDay = endMonthDays[11] + endMonthLengths[11]; - - int maxMonthLength = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]; - int leastMaxMonthLength = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]; + // Insert the final epochDay + epochMonths[epochMonth++] = epochDay; - if (maxMonthLength < startMonthLength) { - maxMonthLength = startMonthLength; - } - if (maxMonthLength < endMonthLength) { - maxMonthLength = endMonthLength; - } - ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH] = maxMonthLength; - - if (leastMaxMonthLength > startMonthLength) { - leastMaxMonthLength = startMonthLength; - } - if (leastMaxMonthLength > endMonthLength) { - leastMaxMonthLength = endMonthLength; - } - ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH] = leastMaxMonthLength; - - int maxMonthDay = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR]; - int leastMaxMonthDay = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]; - - if (maxMonthDay < startMonthDay) { - maxMonthDay = startMonthDay; - } - if (maxMonthDay < endMonthDay) { - maxMonthDay = endMonthDay; + if (epochMonth != epochMonths.length) { + throw new IllegalStateException("Did not fill epochMonths exactly: ndx = " + epochMonth + + " should be " + epochMonths.length); } - ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR] = maxMonthDay; - - if (leastMaxMonthDay > startMonthDay) { - leastMaxMonthDay = startMonthDay; - } - if (leastMaxMonthDay > endMonthDay) { - leastMaxMonthDay = endMonthDay; - } - ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR] = leastMaxMonthDay; + return epochMonths; } /** - * Package private Entry for suppling deviations from the Reader. - * Each entry consists of a range using the Hijrah calendar, - * start year, month, end year, end month, and an offset. - * The offset is used to modify the length of the month +2, +1, -1, -2. + * Parses the 12 months lengths from a property value for a specific year. + * + * @param line the value of a year property + * @return an array of int[12] containing the 12 month lengths + * @throws IllegalArgumentException if the number of months is not 12 + * @throws NumberFormatException if the 12 tokens are not numbers */ - static final class Deviation { - - Deviation(int startYear, int startMonth, int endYear, int endMonth, int offset) { - this.startYear = startYear; - this.startMonth = startMonth; - this.endYear = endYear; - this.endMonth = endMonth; - this.offset = offset; + private int[] parseMonths(String line) { + int[] months = new int[12]; + String[] numbers = line.split("\\s"); + if (numbers.length != 12) { + throw new IllegalArgumentException("wrong number of months on line: " + Arrays.toString(numbers) + "; count: " + numbers.length); } - - final int startYear; - final int startMonth; - final int endYear; - final int endMonth; - final int offset; + for (int i = 0; i < 12; i++) { + try { + months[i] = Integer.valueOf(numbers[i]); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("bad key: " + numbers[i]); + } + } + return months; + } - int getStartYear() { - return startYear; - } - - int getStartMonth() { - return startMonth; - } - - int getEndYear() { - return endYear; - } - - int getEndMonth() { - return endMonth; - } - - int getOffset() { - return offset; - } - - @Override - public String toString() { - return String.format("[year: %4d, month: %2d, offset: %+d]", startYear, startMonth, offset); + /** + * Parse yyyy-MM-dd into a 3 element array [yyyy, mm, dd]. + * + * @param string the input string + * @return the 3 element array with year, month, day + */ + private int[] parseYMD(String string) { + // yyyy-MM-dd + string = string.trim(); + try { + if (string.charAt(4) != '-' || string.charAt(7) != '-') { + throw new IllegalArgumentException("date must be yyyy-MM-dd"); + } + int[] ymd = new int[3]; + ymd[0] = Integer.valueOf(string.substring(0, 4)); + ymd[1] = Integer.valueOf(string.substring(5, 7)); + ymd[2] = Integer.valueOf(string.substring(8, 10)); + return ymd; + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("date must be yyyy-MM-dd", ex); } } - } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/HijrahDate.java --- a/src/share/classes/java/time/chrono/HijrahDate.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/HijrahDate.java Sat Apr 13 21:51:36 2013 +0100 @@ -70,55 +70,38 @@ import java.io.Serializable; import java.time.Clock; import java.time.DateTimeException; -import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalTime; import java.time.Period; import java.time.ZoneId; import java.time.temporal.ChronoField; -import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; -import java.util.Objects; /** * A date in the Hijrah calendar system. *

    - * This date operates using the {@linkplain HijrahChronology Hijrah calendar}. + * This date operates using one of several variants of the + * {@linkplain HijrahChronology Hijrah calendar}. *

    * The Hijrah calendar has a different total of days in a year than - * Gregorian calendar, and a month is based on the period of a complete - * revolution of the moon around the earth (as between successive new moons). - * The calendar cycles becomes longer and unstable, and sometimes a manual - * adjustment (for entering deviation) is necessary for correctness - * because of the complex algorithm. + * Gregorian calendar, and the length of each month is based on the period + * of a complete revolution of the moon around the earth + * (as between successive new moons). + * Refer to the {@link HijrahChronology} for details of supported variants. *

    - * HijrahDate supports the manual adjustment feature by providing a configuration - * file. The configuration file contains the adjustment (deviation) data with following format. - *

    - *   StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 2, -1, or -2)
    - *   Line separator or ";" is used for the separator of each deviation data.
    - * Here is the example. - *
    - *     1429/0-1429/1:1
    - *     1429/2-1429/7:1;1429/6-1429/11:1
    - *     1429/11-9999/11:1
    - * The default location of the configuration file is: - *
    - *   $CLASSPATH/java/time/i18n
    - * And the default file name is: - *
    - *   hijrah_deviation.cfg
    - * The default location and file name can be overriden by setting - * following two Java's system property. - *
    - *   Location: java.time.i18n.HijrahDate.deviationConfigDir
    - *   File name: java.time.i18n.HijrahDate.deviationConfigFile
    - * + * Each HijrahDate is created bound to a particular HijrahChronology, + * The same chronology is propagated to each HijrahDate computed from the date. + * To use a different Hijrah variant, its HijrahChronology can be used + * to create new HijrahDate instances. + * Alternatively, the {@link #withVariant} method can be used to convert + * to a new HijrahChronology. *

    Specification for implementors

    * This class is immutable and thread-safe. * @@ -132,19 +115,14 @@ * Serialization version. */ private static final long serialVersionUID = -5207853542612002020L; - /** * The Chronology of this HijrahDate. */ private final HijrahChronology chrono; /** - * The era. + * The proleptic year. */ - private final transient HijrahEra era; - /** - * The year. - */ - private final transient int yearOfEra; + private final transient int prolepticYear; /** * The month-of-year. */ @@ -153,69 +131,36 @@ * The day-of-month. */ private final transient int dayOfMonth; - /** - * The day-of-year. - */ - private final transient int dayOfYear; - /** - * The day-of-week. - */ - private final transient DayOfWeek dayOfWeek; - /** - * Gregorian days for this object. Holding number of days since 1970/01/01. - * The number of days are calculated with pure Gregorian calendar - * based. - */ - private final long gregorianEpochDay; - /** - * True if year is leap year. - */ - private final transient boolean isLeapYear; //------------------------------------------------------------------------- /** - * Obtains an instance of {@code HijrahDate} from the Hijrah era year, - * month-of-year and day-of-month. This uses the Hijrah era. + * Obtains an instance of {@code HijrahDate} from the Hijrah proleptic year, + * month-of-year and day-of-month. * - * @param prolepticYear the proleptic year to represent in the Hijrah + * @param prolepticYear the proleptic year to represent in the Hijrah calendar * @param monthOfYear the month-of-year to represent, from 1 to 12 * @param dayOfMonth the day-of-month to represent, from 1 to 30 * @return the Hijrah date, never null * @throws DateTimeException if the value of any field is out of range */ static HijrahDate of(HijrahChronology chrono, int prolepticYear, int monthOfYear, int dayOfMonth) { - return (prolepticYear >= 1) ? - HijrahDate.of(chrono, HijrahEra.AH, prolepticYear, monthOfYear, dayOfMonth) : - HijrahDate.of(chrono, HijrahEra.BEFORE_AH, 1 - prolepticYear, monthOfYear, dayOfMonth); + return new HijrahDate(chrono, prolepticYear, monthOfYear, dayOfMonth); } /** - * Obtains an instance of {@code HijrahDate} from the era, year-of-era - * month-of-year and day-of-month. - * - * @param era the era to represent, not null - * @param yearOfEra the year-of-era to represent, from 1 to 9999 - * @param monthOfYear the month-of-year to represent, from 1 to 12 - * @param dayOfMonth the day-of-month to represent, from 1 to 31 - * @return the Hijrah date, never null - * @throws DateTimeException if the value of any field is out of range + * Returns a HijrahDate for the chronology and epochDay. + * @param chrono The Hijrah chronology + * @param epochDay the epoch day + * @return a HijrahDate for the epoch day; non-null */ - private static HijrahDate of(HijrahChronology chrono, HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth) { - Objects.requireNonNull(era, "era"); - chrono.checkValidYearOfEra(yearOfEra); - chrono.checkValidMonth(monthOfYear); - chrono.checkValidDayOfMonth(dayOfMonth); - long gregorianDays = chrono.getGregorianEpochDay(era.prolepticYear(yearOfEra), monthOfYear, dayOfMonth); - return new HijrahDate(chrono, gregorianDays); - } - static HijrahDate ofEpochDay(HijrahChronology chrono, long epochDay) { return new HijrahDate(chrono, epochDay); } //----------------------------------------------------------------------- /** - * Obtains the current {@code HijrahDate} from the system clock in the default time-zone. + * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar + * in the default time-zone. *

    * This will query the {@link Clock#systemDefaultZone() system clock} in the default * time-zone to obtain the current date. @@ -230,7 +175,8 @@ } /** - * Obtains the current {@code HijrahDate} from the system clock in the specified time-zone. + * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar + * in the specified time-zone. *

    * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. * Specifying the time-zone avoids dependence on the default time-zone. @@ -246,7 +192,8 @@ } /** - * Obtains the current {@code HijrahDate} from the specified clock. + * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar + * from the specified clock. *

    * This will query the specified clock to obtain the current date - today. * Using this method allows the use of an alternate clock for testing. @@ -261,8 +208,8 @@ } /** - * Obtains a {@code HijrahDate} representing a date in the Hijrah calendar - * system from the proleptic-year, month-of-year and day-of-month fields. + * Obtains a {@code HijrahDate} of the Islamic Umm Al-Qura calendar + * from the proleptic-year, month-of-year and day-of-month fields. *

    * This returns a {@code HijrahDate} with the specified fields. * The day must be valid for the year and month, otherwise an exception will be thrown. @@ -279,7 +226,7 @@ } /** - * Obtains a {@code HijrahDate} from a temporal object. + * Obtains a {@code HijrahDate} of the Islamic Umm Al-Qura calendar from a temporal object. *

    * This obtains a date in the Hijrah calendar system based on the specified temporal. * A {@code TemporalAccessor} represents an arbitrary set of date and time information, @@ -301,35 +248,93 @@ //----------------------------------------------------------------------- /** - * Constructs an instance with the specified date. + * Constructs an {@code HijrahDate} with the proleptic-year, month-of-year and + * day-of-month fields. * - * @param gregorianDay the number of days from 0001/01/01 (Gregorian), caller calculated + * @param chrono The chronology to create the date with + * @param prolepticYear the proleptic year + * @param monthOfYear the month of year + * @param dayOfMonth the day of month */ - private HijrahDate(HijrahChronology chrono, long gregorianDay) { - this.chrono = chrono; - int[] dateInfo = chrono.getHijrahDateInfo(gregorianDay); + private HijrahDate(HijrahChronology chrono, int prolepticYear, int monthOfYear, int dayOfMonth) { + // Computing the Gregorian day checks the valid ranges + chrono.getEpochDay(prolepticYear, monthOfYear, dayOfMonth); - chrono.checkValidYearOfEra(dateInfo[1]); - chrono.checkValidMonth(dateInfo[2]); - chrono.checkValidDayOfMonth(dateInfo[3]); - chrono.checkValidDayOfYear(dateInfo[4]); + this.chrono = chrono; + this.prolepticYear = prolepticYear; + this.monthOfYear = monthOfYear; + this.dayOfMonth = dayOfMonth; + } - this.era = HijrahEra.of(dateInfo[0]); - this.yearOfEra = dateInfo[1]; - this.monthOfYear = dateInfo[2]; - this.dayOfMonth = dateInfo[3]; - this.dayOfYear = dateInfo[4]; - this.dayOfWeek = DayOfWeek.of(dateInfo[5]); - this.gregorianEpochDay = gregorianDay; - this.isLeapYear = chrono.isLeapYear(this.yearOfEra); + /** + * Constructs an instance with the Epoch Day. + * + * @param epochDay the epochDay + */ + private HijrahDate(HijrahChronology chrono, long epochDay) { + int[] dateInfo = chrono.getHijrahDateInfo((int)epochDay); + + this.chrono = chrono; + this.prolepticYear = dateInfo[0]; + this.monthOfYear = dateInfo[1]; + this.dayOfMonth = dateInfo[2]; } //----------------------------------------------------------------------- + /** + * Gets the chronology of this date, which is the Hijrah calendar system. + *

    + * The {@code Chronology} represents the calendar system in use. + * The era and other fields in {@link ChronoField} are defined by the chronology. + * + * @return the Hijrah chronology, not null + */ @Override public HijrahChronology getChronology() { return chrono; } + /** + * Gets the era applicable at this date. + *

    + * The Hijrah calendar system has one era, 'AH', + * defined by {@link HijrahEra}. + * + * @return the era applicable at this date, not null + */ + @Override + public HijrahEra getEra() { + return HijrahEra.AH; + } + + /** + * Returns the length of the month represented by this date. + *

    + * This returns the length of the month in days. + * Month lengths in the Hijrah calendar system vary between 29 and 30 days. + * + * @return the length of the month in days + */ + @Override + public int lengthOfMonth() { + return chrono.getMonthLength(prolepticYear, monthOfYear); + } + + /** + * Returns the length of the year represented by this date. + *

    + * This returns the length of the year in days. + * A Hijrah calendar system year is typically shorter than + * that of the ISO calendar system. + * + * @return the length of the year in days + */ + @Override + public int lengthOfYear() { + return chrono.getYearLength(prolepticYear); + } + + //----------------------------------------------------------------------- @Override public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { @@ -339,83 +344,106 @@ case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5); // TODO - case YEAR_OF_ERA: return ValueRange.of(1, 1000); // TODO + // TODO does the limited range of valid years cause years to + // start/end part way through? that would affect range } return getChronology().range(f); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.rangeRefinedBy(this); } - @Override // Override for javadoc - public int get(TemporalField field) { - return super.get(field); - } - @Override public long getLong(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { - case DAY_OF_WEEK: return dayOfWeek.getValue(); - case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((dayOfWeek.getValue() - 1) % 7) + 1; - case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((dayOfYear - 1) % 7) + 1; + case DAY_OF_WEEK: return getDayOfWeek(); + case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((getDayOfWeek() - 1) % 7) + 1; + case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1; case DAY_OF_MONTH: return this.dayOfMonth; - case DAY_OF_YEAR: return this.dayOfYear; + case DAY_OF_YEAR: return this.getDayOfYear(); case EPOCH_DAY: return toEpochDay(); case ALIGNED_WEEK_OF_MONTH: return ((dayOfMonth - 1) / 7) + 1; - case ALIGNED_WEEK_OF_YEAR: return ((dayOfYear - 1) / 7) + 1; + case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1; case MONTH_OF_YEAR: return monthOfYear; - case YEAR_OF_ERA: return yearOfEra; - case YEAR: return yearOfEra; - case ERA: return era.getValue(); + case PROLEPTIC_MONTH: return getProlepticMonth(); + case YEAR_OF_ERA: return prolepticYear; + case YEAR: return prolepticYear; + case ERA: return getEraValue(); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.getFrom(this); } + private long getProlepticMonth() { + return prolepticYear * 12L + monthOfYear - 1; + } + @Override public HijrahDate with(TemporalField field, long newValue) { if (field instanceof ChronoField) { ChronoField f = (ChronoField) field; - f.checkValidValue(newValue); // TODO: validate value + // not using checkValidIntValue so EPOCH_DAY and PROLEPTIC_MONTH work + chrono.range(f).checkValidValue(newValue, f); // TODO: validate value int nvalue = (int) newValue; switch (f) { - case DAY_OF_WEEK: return plusDays(newValue - dayOfWeek.getValue()); + case DAY_OF_WEEK: return plusDays(newValue - getDayOfWeek()); case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); - case DAY_OF_MONTH: return resolvePreviousValid(yearOfEra, monthOfYear, nvalue); - case DAY_OF_YEAR: return resolvePreviousValid(yearOfEra, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); - case EPOCH_DAY: return new HijrahDate(chrono, nvalue); + case DAY_OF_MONTH: return resolvePreviousValid(prolepticYear, monthOfYear, nvalue); + case DAY_OF_YEAR: return resolvePreviousValid(prolepticYear, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); + case EPOCH_DAY: return new HijrahDate(chrono, newValue); case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7); case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7); - case MONTH_OF_YEAR: return resolvePreviousValid(yearOfEra, nvalue, dayOfMonth); - case YEAR_OF_ERA: return resolvePreviousValid(yearOfEra >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth); + case MONTH_OF_YEAR: return resolvePreviousValid(prolepticYear, nvalue, dayOfMonth); + case PROLEPTIC_MONTH: return plusMonths(newValue - getProlepticMonth()); + case YEAR_OF_ERA: return resolvePreviousValid(prolepticYear >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth); case YEAR: return resolvePreviousValid(nvalue, monthOfYear, dayOfMonth); - case ERA: return resolvePreviousValid(1 - yearOfEra, monthOfYear, dayOfMonth); + case ERA: return resolvePreviousValid(1 - prolepticYear, monthOfYear, dayOfMonth); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } - return (HijrahDate) ChronoLocalDate.super.with(field, newValue); + return ChronoLocalDate.super.with(field, newValue); } - private HijrahDate resolvePreviousValid(int yearOfEra, int month, int day) { - int monthDays = getMonthDays(month - 1, yearOfEra); + private HijrahDate resolvePreviousValid(int prolepticYear, int month, int day) { + int monthDays = chrono.getMonthLength(prolepticYear, month); if (day > monthDays) { day = monthDays; } - return HijrahDate.of(chrono, yearOfEra, month, day); + return HijrahDate.of(chrono, prolepticYear, month, day); } /** * {@inheritDoc} - * @throws DateTimeException {@inheritDoc} + * @throws DateTimeException if unable to make the adjustment. + * For example, if the adjuster requires an ISO chronology * @throws ArithmeticException {@inheritDoc} */ @Override public HijrahDate with(TemporalAdjuster adjuster) { - return (HijrahDate)super.with(adjuster); + return super.with(adjuster); + } + + /** + * Returns a {@code HijrahDate} with the Chronology requested. + *

    + * The year, month, and day are checked against the new requested + * HijrahChronology. If the chronology has a shorter month length + * for the month, the day is reduced to be the last day of the month. + * + * @param chronology the new HijrahChonology, non-null + * @return a HijrahDate with the requested HijrahChronology, non-null + */ + public HijrahDate withVariant(HijrahChronology chronology) { + if (chrono == chronology) { + return this; + } + // Like resolvePreviousValid the day is constrained to stay in the same month + int monthDays = chronology.getDayOfYear(prolepticYear, monthOfYear); + return HijrahDate.of(chronology, prolepticYear, monthOfYear,(dayOfMonth > monthDays) ? monthDays : dayOfMonth ); } /** @@ -425,7 +453,7 @@ */ @Override public HijrahDate plus(TemporalAmount amount) { - return (HijrahDate)super.plus(amount); + return super.plus(amount); } /** @@ -435,12 +463,42 @@ */ @Override public HijrahDate minus(TemporalAmount amount) { - return (HijrahDate)super.minus(amount); + return super.minus(amount); } @Override public long toEpochDay() { - return chrono.getGregorianEpochDay(yearOfEra, monthOfYear, dayOfMonth); + return chrono.getEpochDay(prolepticYear, monthOfYear, dayOfMonth); + } + + /** + * Gets the day-of-year field. + *

    + * This method returns the primitive {@code int} value for the day-of-year. + * + * @return the day-of-year + */ + private int getDayOfYear() { + return chrono.getDayOfYear(prolepticYear, monthOfYear); + } + + /** + * Gets the day-of-week value. + * + * @return the day-of-week; computed from the epochday + */ + private int getDayOfWeek() { + int dow0 = (int)Math.floorMod(toEpochDay() + 3, 7); + return dow0 + 1; + } + + /** + * Gets the Era of this date. + * + * @return the Era of this date; computed from epochDay + */ + private int getEraValue() { + return (prolepticYear > 1 ? 1 : 0); } //----------------------------------------------------------------------- @@ -451,7 +509,7 @@ */ @Override public boolean isLeapYear() { - return this.isLeapYear; + return chrono.isLeapYear(prolepticYear); } //----------------------------------------------------------------------- @@ -460,111 +518,72 @@ if (years == 0) { return this; } - int newYear = Math.addExact(this.yearOfEra, (int)years); - return HijrahDate.of(chrono, this.era, newYear, this.monthOfYear, this.dayOfMonth); + int newYear = Math.addExact(this.prolepticYear, (int)years); + return resolvePreviousValid(newYear, monthOfYear, dayOfMonth); } @Override - HijrahDate plusMonths(long months) { - if (months == 0) { + HijrahDate plusMonths(long monthsToAdd) { + if (monthsToAdd == 0) { return this; } - int newMonth = this.monthOfYear - 1; - newMonth = newMonth + (int)months; - int years = newMonth / 12; - newMonth = newMonth % 12; - while (newMonth < 0) { - newMonth += 12; - years = Math.subtractExact(years, 1); - } - int newYear = Math.addExact(this.yearOfEra, years); - return HijrahDate.of(chrono, this.era, newYear, newMonth + 1, this.dayOfMonth); + long monthCount = prolepticYear * 12L + (monthOfYear - 1); + long calcMonths = monthCount + monthsToAdd; // safe overflow + int newYear = chrono.checkValidYear(Math.floorDiv(calcMonths, 12L)); + int newMonth = (int)Math.floorMod(calcMonths, 12L) + 1; + return resolvePreviousValid(newYear, newMonth, dayOfMonth); } @Override HijrahDate plusWeeks(long weeksToAdd) { - return (HijrahDate)super.plusWeeks(weeksToAdd); + return super.plusWeeks(weeksToAdd); } @Override HijrahDate plusDays(long days) { - return new HijrahDate(chrono, this.gregorianEpochDay + days); + return new HijrahDate(chrono, toEpochDay() + days); } @Override public HijrahDate plus(long amountToAdd, TemporalUnit unit) { - return (HijrahDate)super.plus(amountToAdd, unit); + return super.plus(amountToAdd, unit); } @Override public HijrahDate minus(long amountToSubtract, TemporalUnit unit) { - return (HijrahDate)super.minus(amountToSubtract, unit); + return super.minus(amountToSubtract, unit); } @Override HijrahDate minusYears(long yearsToSubtract) { - return (HijrahDate)super.minusYears(yearsToSubtract); + return super.minusYears(yearsToSubtract); } @Override HijrahDate minusMonths(long monthsToSubtract) { - return (HijrahDate)super.minusMonths(monthsToSubtract); + return super.minusMonths(monthsToSubtract); } @Override HijrahDate minusWeeks(long weeksToSubtract) { - return (HijrahDate)super.minusWeeks(weeksToSubtract); + return super.minusWeeks(weeksToSubtract); } @Override HijrahDate minusDays(long daysToSubtract) { - return (HijrahDate)super.minusDays(daysToSubtract); - } - - /** - * Returns month days from the beginning of year. - * - * @param month month (0-based) - * @parma year year - * @return month days from the beginning of year - */ - private int getMonthDays(int month, int year) { - int[] newMonths = chrono.getAdjustedMonthDays(year); - return newMonths[month]; - } - - /** - * Returns month length. - * - * @param month month (0-based) - * @param year year - * @return month length - */ - private int getMonthLength(int month, int year) { - int[] newMonths = chrono.getAdjustedMonthLength(year); - return newMonths[month]; - } - - @Override - public int lengthOfMonth() { - return getMonthLength(monthOfYear - 1, yearOfEra); - } - - @Override - public int lengthOfYear() { - return chrono.getYearLength(yearOfEra); // TODO: proleptic year + return super.minusDays(daysToSubtract); } @Override // for javadoc and covariant return type public final ChronoLocalDateTime atTime(LocalTime localTime) { - return (ChronoLocalDateTime)super.atTime(localTime); + return super.atTime(localTime); } @Override public Period periodUntil(ChronoLocalDate endDate) { // TODO: untested - HijrahDate end = (HijrahDate) getChronology().date(endDate); - long totalMonths = (end.yearOfEra - this.yearOfEra) * 12 + (end.monthOfYear - this.monthOfYear); // safe + HijrahDate end = getChronology().date(endDate); + long totalMonths = (end.prolepticYear - this.prolepticYear) * 12 + (end.monthOfYear - this.monthOfYear); // safe int days = end.dayOfMonth - this.dayOfMonth; if (totalMonths > 0 && days < 0) { totalMonths--; @@ -604,7 +623,7 @@ } static ChronoLocalDate readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - HijrahChronology chrono = (HijrahChronology)in.readObject(); + HijrahChronology chrono = (HijrahChronology) in.readObject(); int year = in.readInt(); int month = in.readByte(); int dayOfMonth = in.readByte(); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/HijrahDeviationReader.java --- a/src/share/classes/java/time/chrono/HijrahDeviationReader.java Sat Apr 13 20:16:00 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,244 +0,0 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of JSR-310 nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package java.time.chrono; - - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.file.FileSystems; -import java.security.AccessController; -import java.text.ParseException; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoField; -import java.util.Arrays; -import java.util.function.Consumer; - -/** - * A reader for Hijrah Deviation files. - *

    - * For each Hijrah calendar a deviation file is used - * to modify the initial Hijrah calendar by changing the length of - * individual months. - *

    - * The default location of the deviation files is - * {@code + FILE_SEP + "lib"}. - * The deviation files are named {@code "hijrah_" + ID}. - *

    - * The deviation file for a calendar can be overridden by defining the - * property {@code java.time.chrono.HijrahChronology.File.hijrah_} - * with the full pathname of the deviation file. - *

    - * The deviation file is read line by line: - *

      - *
    • The "#" character begins a comment - - * all characters including and after the "#" on the line are ignored
    • - *
    • Valid lines contain two fields, separated by whitespace, whitespace - * is otherwise ignored.
    • - *
    • The first field is a LocalDate using the format "MMM-dd-yyyy"; - * The LocalDate must be converted to a HijrahDate using - * the {@code islamic} calendar.
    • - *
    • The second field is the offset, +2, +1, -1, -2 as parsed - * by Integer.valueOf to modify the length of the Hijrah month.
    • - *
    • Empty lines are ignore.
    • - *
    • Exceptions are throw for invalid formatted dates and offset, - * and other I/O errors on the file. - *
    - *

    Example:

    - *
    # Deviation data for islamicc calendar
    - * Mar-23-2012 -1
    - * Apr-22-2012 +1
    - * May-21-2012 -1
    - * Dec-14-2012 +1
    - * 
    - * - * @since 1.8 - */ -final class HijrahDeviationReader { - - /** - * Default prefix for name of deviation file; suffix is typeId. - */ - private static final String DEFAULT_CONFIG_FILE_PREFIX = "hijrah_"; - - /** - * Read Hijrah_deviation.cfg file. The config file contains the deviation - * data with format defined in the class javadoc. - * - * @param typeId the name of the calendar - * @param calendarType the calendar type - * @return {@code true} if the file was read and each entry accepted by the - * Consumer; else {@code false} no configuration was done - * - * @throws IOException for zip/jar file handling exception. - * @throws ParseException if the format of the configuration file is wrong. - */ - static boolean readDeviation(String typeId, String calendarType, - Consumer consumer) throws IOException, ParseException { - InputStream is = getConfigFileInputStream(typeId); - if (is != null) { - try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { - String line = ""; - int num = 0; - while ((line = br.readLine()) != null) { - num++; - HijrahChronology.Deviation entry = parseLine(line, num); - if (entry != null) { - consumer.accept(entry); - } - } - } - return true; - } - return false; - } - - /** - * Parse each deviation element. - * - * @param line a line to parse - * @param num line number - * @return an Entry or null if the line is empty. - * @throws ParseException if line has incorrect format. - */ - private static HijrahChronology.Deviation parseLine(final String line, final int num) throws ParseException { - int hash = line.indexOf("#"); - String nocomment = (hash < 0) ? line : line.substring(0, hash); - String[] split = nocomment.split("\\s"); - if (split.length == 0 || split[0].isEmpty()) { - return null; // Nothing to parse - } - if (split.length != 2) { - throw new ParseException("Less than 2 tokens on line : " + line + Arrays.toString(split) + ", split.length: " + split.length, num); - } - - //element [0] is a date - //element [1] is the offset - - LocalDate isoDate = DateTimeFormatter.ofPattern("MMM-dd-yyyy").parse(split[0], LocalDate::from); - int offset = Integer.valueOf(split[1]); - - // Convert date to HijrahDate using the default Islamic Calendar - - HijrahDate hijrahDate = HijrahChronology.INSTANCE.date(isoDate); - - int year = hijrahDate.get(ChronoField.YEAR); - int month = hijrahDate.get(ChronoField.MONTH_OF_YEAR); - return new HijrahChronology.Deviation(year, month, year, month, offset); - } - - - /** - * Return InputStream for deviation configuration file. The default location - * of the deviation file is: - *
    -     *   $CLASSPATH/java/time/calendar
    -     * 
    And the default file name is: - *
    -     *   hijrah_ + typeId + .cfg
    -     * 
    The default location and file name can be overridden by setting - * following two Java system properties. - *
    -     *   Location: java.time.chrono.HijrahDate.deviationConfigDir
    -     *   File name: java.time.chrono.HijrahDate.File. + typeid
    -     * 
    Regarding the file format, see readDeviationConfig() method for - * details. - * - * @param typeId the name of the calendar deviation data - * @return InputStream for file reading. - * @throws IOException for zip/jar file handling exception. - */ - private static InputStream getConfigFileInputStream(final String typeId) throws IOException { - try { - InputStream stream = AccessController - .doPrivileged((java.security.PrivilegedExceptionAction) () -> { - String propFilename = "java.time.chrono.HijrahChronology.File." + typeId; - String filename = System.getProperty(propFilename); - File file = null; - if (filename != null) { - file = new File(filename); - } else { - String libDir = System.getProperty("java.home") + File.separator + "lib"; - try { - libDir = FileSystems.getDefault().getPath(libDir).toRealPath().toString(); - } catch(Exception e) {} - filename = DEFAULT_CONFIG_FILE_PREFIX + typeId + ".cfg"; - file = new File(libDir, filename); - } - - if (file.exists()) { - try { - return new FileInputStream(file); - } catch (IOException ioe) { - throw ioe; - } - } else { - return null; - } - }); - return stream; - } catch (Exception ex) { - ex.printStackTrace(); - // Not working - return null; - } - } -} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/HijrahEra.java --- a/src/share/classes/java/time/chrono/HijrahEra.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/HijrahEra.java Sat Apr 13 21:51:36 2013 +0100 @@ -24,6 +24,11 @@ */ /* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos * * All rights reserved. @@ -56,16 +61,22 @@ */ package java.time.chrono; +import static java.time.temporal.ChronoField.ERA; + import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.time.DateTimeException; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalField; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.time.temporal.ValueRange; /** * An era in the Hijrah calendar system. *

    - * The Hijrah calendar system has two eras. - * The date {@code 0001-01-01 (Hijrah)} is {@code 622-06-19 (ISO)}. + * The Hijrah calendar system has only one era covering the + * proleptic years greater than zero. *

    * Do not use {@code ordinal()} to obtain the numeric representation of {@code HijrahEra}. * Use {@code getValue()} instead. @@ -75,80 +86,76 @@ * * @since 1.8 */ -enum HijrahEra implements Era { +public enum HijrahEra implements Era { /** - * The singleton instance for the era before the current one, 'Before Anno Hegirae', - * which has the value 0. - */ - BEFORE_AH, - /** - * The singleton instance for the current era, 'Anno Hegirae', which has the value 1. + * The singleton instance for the current era, 'Anno Hegirae', + * which has the numeric value 1. */ AH; //----------------------------------------------------------------------- /** - * Obtains an instance of {@code HijrahEra} from a value. + * Obtains an instance of {@code HijrahEra} from an {@code int} value. *

    - * The current era (from ISO date 622-06-19 onwards) has the value 1 - * The previous era has the value 0. + * The current era, which is the only accepted value, has the value 1 * - * @param hijrahEra the era to represent, from 0 to 1 - * @return the HijrahEra singleton, never null - * @throws DateTimeException if the era is invalid + * @param hijrahEra the era to represent, only 1 supported + * @return the HijrahEra.AH singleton, not null + * @throws DateTimeException if the value is invalid */ public static HijrahEra of(int hijrahEra) { - switch (hijrahEra) { - case 0: - return BEFORE_AH; - case 1: - return AH; - default: - throw new DateTimeException("HijrahEra not valid"); + if (hijrahEra == 1 ) { + return AH; + } else { + throw new DateTimeException("Invalid era: " + hijrahEra); } } //----------------------------------------------------------------------- /** - * Gets the era numeric value. + * Gets the numeric era {@code int} value. *

    - * The current era (from ISO date 622-06-19 onwards) has the value 1. - * The previous era has the value 0. + * The era AH has the value 1. * - * @return the era value, from 0 (BEFORE_AH) to 1 (AH) + * @return the era value, 1 (AH) */ @Override public int getValue() { - return ordinal(); - } - - @Override - public HijrahChronology getChronology() { - return HijrahChronology.INSTANCE; - } - - // JDK8 default methods: - //----------------------------------------------------------------------- - @Override - public HijrahDate date(int year, int month, int day) { - return (HijrahDate)(getChronology().date(this, year, month, day)); + return 1; } - @Override - public HijrahDate dateYearDay(int year, int dayOfYear) { - return (HijrahDate)(getChronology().dateYearDay(this, year, dayOfYear)); - } - - //------------------------------------------------------------------------- + //----------------------------------------------------------------------- /** - * Returns the proleptic year from this era and year of era. + * Gets the range of valid values for the specified field. + *

    + * The range object expresses the minimum and maximum valid values for a field. + * This era is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

    + * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code ERA} field returns the range. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. + *

    + * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + *

    + * The {@code ERA} field returns a range for the one valid Hijrah era. * - * @param yearOfEra the year of Era - * @return the computed prolepticYear + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the unit is not supported */ - int prolepticYear(int yearOfEra) { - return (this == HijrahEra.AH ? yearOfEra : 1 - yearOfEra); + @Override // override as super would return range from 0 to 1 + public ValueRange range(TemporalField field) { + if (field == ERA) { + return ValueRange.of(1, 1); + } + return Era.super.range(field); } //----------------------------------------------------------------------- diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/IsoChronology.java --- a/src/share/classes/java/time/chrono/IsoChronology.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/IsoChronology.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,20 +61,41 @@ */ package java.time.chrono; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.TemporalAdjuster.nextOrSame; + import java.io.Serializable; import java.time.Clock; import java.time.DateTimeException; +import java.time.DayOfWeek; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.Month; +import java.time.Year; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.ResolverStyle; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; import java.time.temporal.ValueRange; import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; /** @@ -111,20 +132,6 @@ * Singleton instance of the ISO chronology. */ public static final IsoChronology INSTANCE = new IsoChronology(); - /** - * The singleton instance for the era BCE - 'Before Current Era'. - * The 'ISO' part of the name emphasizes that this differs from the BCE - * era in the Gregorian calendar system. - * This has the numeric value of {@code 0}. - */ - public static final Era ERA_BCE = IsoEra.BCE; - /** - * The singleton instance for the era CE - 'Current Era'. - * The 'ISO' part of the name emphasizes that this differs from the CE - * era in the Gregorian calendar system. - * This has the numeric value of {@code 1}. - */ - public static final Era ERA_CE = IsoEra.CE; /** * Serialization version. @@ -137,15 +144,6 @@ private IsoChronology() { } - /** - * Resolve singleton. - * - * @return the singleton instance, not null - */ - private Object readResolve() { - return INSTANCE; - } - //----------------------------------------------------------------------- /** * Gets the ID of the chronology - 'ISO'. @@ -189,6 +187,7 @@ * @param dayOfMonth the ISO day-of-month * @return the ISO local date, not null * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the type of {@code era} is not {@code IsoEra} */ @Override // override with covariant return type public LocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) { @@ -241,6 +240,20 @@ return LocalDate.ofYearDay(prolepticYear, dayOfYear); } + /** + * Obtains an ISO local date from the epoch-day. + *

    + * This is equivalent to {@link LocalDate#ofEpochDay(long)}. + * + * @param epochDay the epoch day + * @return the ISO local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate dateEpochDay(long epochDay) { + return LocalDate.ofEpochDay(epochDay); + } + //----------------------------------------------------------------------- /** * Obtains an ISO local date from another date-time object. @@ -379,7 +392,7 @@ @Override public int prolepticYear(Era era, int yearOfEra) { if (era instanceof IsoEra == false) { - throw new DateTimeException("Era must be IsoEra"); + throw new ClassCastException("Era must be IsoEra"); } return (era == IsoEra.CE ? yearOfEra : 1 - yearOfEra); } @@ -395,6 +408,282 @@ } //----------------------------------------------------------------------- + /** + * Resolves parsed {@code ChronoField} values into a date during parsing. + *

    + * Most {@code TemporalField} implementations are resolved using the + * resolve method on the field. By contrast, the {@code ChronoField} class + * defines fields that only have meaning relative to the chronology. + * As such, {@code ChronoField} date fields are resolved here in the + * context of a specific chronology. + *

    + * {@code ChronoField} instances on the ISO calendar system are resolved + * as follows. + *

      + *
    • {@code EPOCH_DAY} - If present, this is converted to a {@code LocalDate} + * all other date fields are then cross-checked against the date + *
    • {@code PROLEPTIC_MONTH} - If present, then it is split into the + * {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart + * then the field is validated. + *
    • {@code YEAR_OF_ERA} and {@code ERA} - If both are present, then they + * are combined to form a {@code YEAR}. In lenient mode, the {@code YEAR_OF_ERA} + * range is not validated, in smart and strict mode it is. The {@code ERA} is + * validated for range in all three modes. If only the {@code YEAR_OF_ERA} is + * present, and the mode is smart or lenient, then the current era (CE/AD) + * is assumed. In strict mode, no ers is assumed and the {@code YEAR_OF_ERA} is + * left untouched. If only the {@code ERA} is present, then it is left untouched. + *
    • {@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} - + * If all three are present, then they are combined to form a {@code LocalDate}. + * In all three modes, the {@code YEAR} is validated. If the mode is smart or strict, + * then the month and day are validated, with the day validated from 1 to 31. + * If the mode is lenient, then the date is combined in a manner equivalent to + * creating a date on the first of January in the requested year, then adding + * the difference in months, then the difference in days. + * If the mode is smart, and the day-of-month is greater than the maximum for + * the year-month, then the day-of-month is adjusted to the last day-of-month. + * If the mode is strict, then the three fields must form a valid date. + *
    • {@code YEAR} and {@code DAY_OF_YEAR} - + * If both are present, then they are combined to form a {@code LocalDate}. + * In all three modes, the {@code YEAR} is validated. + * If the mode is lenient, then the date is combined in a manner equivalent to + * creating a date on the first of January in the requested year, then adding + * the difference in days. + * If the mode is smart or strict, then the two fields must form a valid date. + *
    • {@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and + * {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} - + * If all four are present, then they are combined to form a {@code LocalDate}. + * In all three modes, the {@code YEAR} is validated. + * If the mode is lenient, then the date is combined in a manner equivalent to + * creating a date on the first of January in the requested year, then adding + * the difference in months, then the difference in weeks, then in days. + * If the mode is smart or strict, then the all four fields are validated to + * their outer ranges. The date is then combined in a manner equivalent to + * creating a date on the first day of the requested year and month, then adding + * the amount in weeks and days to reach their values. If the mode is strict, + * the date is additionally validated to check that the day and week adjustment + * did not change the month. + *
    • {@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and + * {@code DAY_OF_WEEK} - If all four are present, then they are combined to + * form a {@code LocalDate}. The approach is the same as described above for + * years, months and weeks in {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}. + * The day-of-week is adjusted as the next or same matching day-of-week once + * the years, months and weeks have been handled. + *
    • {@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} - + * If all three are present, then they are combined to form a {@code LocalDate}. + * In all three modes, the {@code YEAR} is validated. + * If the mode is lenient, then the date is combined in a manner equivalent to + * creating a date on the first of January in the requested year, then adding + * the difference in weeks, then in days. + * If the mode is smart or strict, then the all three fields are validated to + * their outer ranges. The date is then combined in a manner equivalent to + * creating a date on the first day of the requested year, then adding + * the amount in weeks and days to reach their values. If the mode is strict, + * the date is additionally validated to check that the day and week adjustment + * did not change the year. + *
    • {@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code DAY_OF_WEEK} - + * If all three are present, then they are combined to form a {@code LocalDate}. + * The approach is the same as described above for years and weeks in + * {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}. The day-of-week is adjusted as the + * next or same matching day-of-week once the years and weeks have been handled. + *
    + * + * @param fieldValues the map of fields to values, which can be updated, not null + * @param resolverStyle the requested type of resolve, not null + * @return the resolved date, null if insufficient information to create a date + * @throws DateTimeException if the date cannot be resolved, typically + * because of a conflict in the input data + */ + @Override // override for performance + public LocalDate resolveDate(Map fieldValues, ResolverStyle resolverStyle) { + // check epoch-day before inventing era + if (fieldValues.containsKey(EPOCH_DAY)) { + return LocalDate.ofEpochDay(fieldValues.remove(EPOCH_DAY)); + } + + // fix proleptic month before inventing era + resolveProlepticMonth(fieldValues, resolverStyle); + + // invent era if necessary to resolve year-of-era + resolveYearOfEra(fieldValues, resolverStyle); + + // build date + if (fieldValues.containsKey(YEAR)) { + if (fieldValues.containsKey(MONTH_OF_YEAR)) { + if (fieldValues.containsKey(DAY_OF_MONTH)) { + return resolveYMD(fieldValues, resolverStyle); + } + if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) { + if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { + return resolveYMAA(fieldValues, resolverStyle); + } + if (fieldValues.containsKey(DAY_OF_WEEK)) { + return resolveYMAD(fieldValues, resolverStyle); + } + } + } + if (fieldValues.containsKey(DAY_OF_YEAR)) { + return resolveYD(fieldValues, resolverStyle); + } + if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) { + if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { + return resolveYAA(fieldValues, resolverStyle); + } + if (fieldValues.containsKey(DAY_OF_WEEK)) { + return resolveYAD(fieldValues, resolverStyle); + } + } + } + return null; + } + + private void resolveProlepticMonth(Map fieldValues, ResolverStyle resolverStyle) { + Long pMonth = fieldValues.remove(PROLEPTIC_MONTH); + if (pMonth != null) { + if (resolverStyle != ResolverStyle.LENIENT) { + PROLEPTIC_MONTH.checkValidValue(pMonth); + } + addFieldValue(fieldValues, MONTH_OF_YEAR, Math.floorMod(pMonth, 12) + 1); + addFieldValue(fieldValues, YEAR, Math.floorDiv(pMonth, 12)); + } + } + + private void resolveYearOfEra(Map fieldValues, ResolverStyle resolverStyle) { + Long yoeLong = fieldValues.remove(YEAR_OF_ERA); + if (yoeLong != null) { + if (resolverStyle != ResolverStyle.LENIENT) { + YEAR_OF_ERA.checkValidValue(yoeLong); + } + Long era = fieldValues.remove(ERA); + if (era == null) { + Long year = fieldValues.get(YEAR); + if (resolverStyle == ResolverStyle.STRICT) { + // do not invent era if strict, but do cross-check with year + if (year != null) { + addFieldValue(fieldValues, YEAR, (year > 0 ? yoeLong: Math.subtractExact(1, yoeLong))); + } else { + // reinstate the field removed earlier, no cross-check issues + fieldValues.put(YEAR_OF_ERA, yoeLong); + } + } else { + // invent era + addFieldValue(fieldValues, YEAR, (year == null || year > 0 ? yoeLong: Math.subtractExact(1, yoeLong))); + } + } else if (era.longValue() == 1L) { + addFieldValue(fieldValues, YEAR, yoeLong); + } else if (era.longValue() == 0L) { + addFieldValue(fieldValues, YEAR, Math.subtractExact(1, yoeLong)); + } else { + throw new DateTimeException("Invalid value for era: " + era); + } + } + } + + private LocalDate resolveYMD(Map fieldValues, ResolverStyle resolverStyle) { + int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR)); + if (resolverStyle == ResolverStyle.LENIENT) { + long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); + long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1); + return LocalDate.of(y, 1, 1).plusMonths(months).plusDays(days); + } + int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR)); + int dom = DAY_OF_MONTH.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH)); + if (resolverStyle == ResolverStyle.SMART) { // previous valid + dom = Math.min(dom, Month.of(moy).length(Year.isLeap(y))); + } + return LocalDate.of(y, moy, dom); + } + + private LocalDate resolveYD(Map fieldValues, ResolverStyle resolverStyle) { + int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR)); + if (resolverStyle == ResolverStyle.LENIENT) { + long days = Math.subtractExact(fieldValues.remove(DAY_OF_YEAR), 1); + return LocalDate.of(y, 1, 1).plusDays(days); + } + int doy = DAY_OF_YEAR.checkValidIntValue(fieldValues.remove(DAY_OF_YEAR)); + return LocalDate.ofYearDay(y, doy); // smart is same as strict + } + + private LocalDate resolveYMAA(Map fieldValues, ResolverStyle resolverStyle) { + int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR)); + if (resolverStyle == ResolverStyle.LENIENT) { + long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); + long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1); + long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), 1); + return LocalDate.of(y, 1, 1).plusMonths(months).plusWeeks(weeks).plusDays(days); + } + int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR)); + int aw = ALIGNED_WEEK_OF_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH)); + int ad = ALIGNED_DAY_OF_WEEK_IN_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH)); + LocalDate date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1)); + if (resolverStyle == ResolverStyle.STRICT && date.getMonthValue() != moy) { + throw new DateTimeException("Strict mode rejected resolved date as it is in a different month"); + } + return date; + } + + private LocalDate resolveYMAD(Map fieldValues, ResolverStyle resolverStyle) { + int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR)); + if (resolverStyle == ResolverStyle.LENIENT) { + long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1); + long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1); + long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1); + return resolveAligned(y, months, weeks, dow); + } + int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR)); + int aw = ALIGNED_WEEK_OF_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH)); + int dow = DAY_OF_WEEK.checkValidIntValue(fieldValues.remove(DAY_OF_WEEK)); + LocalDate date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow))); + if (resolverStyle == ResolverStyle.STRICT && date.getMonthValue() != moy) { + throw new DateTimeException("Strict mode rejected resolved date as it is in a different month"); + } + return date; + } + + private LocalDate resolveYAA(Map fieldValues, ResolverStyle resolverStyle) { + int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR)); + if (resolverStyle == ResolverStyle.LENIENT) { + long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1); + long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), 1); + return LocalDate.of(y, 1, 1).plusWeeks(weeks).plusDays(days); + } + int aw = ALIGNED_WEEK_OF_YEAR.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR)); + int ad = ALIGNED_DAY_OF_WEEK_IN_YEAR.checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR)); + LocalDate date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1)); + if (resolverStyle == ResolverStyle.STRICT && date.getYear() != y) { + throw new DateTimeException("Strict mode rejected resolved date as it is in a different year"); + } + return date; + } + + private LocalDate resolveYAD(Map fieldValues, ResolverStyle resolverStyle) { + int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR)); + if (resolverStyle == ResolverStyle.LENIENT) { + long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1); + long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1); + return resolveAligned(y, 0, weeks, dow); + } + int aw = ALIGNED_WEEK_OF_YEAR.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR)); + int dow = DAY_OF_WEEK.checkValidIntValue(fieldValues.remove(DAY_OF_WEEK)); + LocalDate date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow))); + if (resolverStyle == ResolverStyle.STRICT && date.getYear() != y) { + throw new DateTimeException("Strict mode rejected resolved date as it is in a different year"); + } + return date; + } + + private LocalDate resolveAligned(int y, long months, long weeks, long dow) { + LocalDate date = LocalDate.of(y, 1, 1).plusMonths(months).plusWeeks(weeks); + if (dow > 7) { + date = date.plusWeeks((dow - 1) / 7); + dow = ((dow - 1) % 7) + 1; + } else if (dow < 1) { + date = date.plusWeeks(Math.subtractExact(dow, 7) / 7); + dow = ((dow + 6) % 7) + 1; + } + return date.with(nextOrSame(DayOfWeek.of((int) dow))); + } + + //----------------------------------------------------------------------- @Override public ValueRange range(ChronoField field) { return field.range(); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/IsoEra.java --- a/src/share/classes/java/time/chrono/IsoEra.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/IsoEra.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,16 +61,38 @@ */ package java.time.chrono; - import java.time.DateTimeException; -import java.time.LocalDate; /** * An era in the ISO calendar system. *

    * The ISO-8601 standard does not define eras. * A definition has therefore been created with two eras - 'Current era' (CE) for - * years from 0001-01-01 (ISO) and 'Before current era' (BCE) for years before that. + * years on or after 0001-01-01 (ISO), and 'Before current era' (BCE) for years before that. + *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    year-of-eraeraproleptic-year
    2CE2
    1CE1
    1BCE0
    2BCE-1
    *

    * Do not use {@code ordinal()} to obtain the numeric representation of {@code IsoEra}. * Use {@code getValue()} instead. @@ -80,20 +102,16 @@ * * @since 1.8 */ -enum IsoEra implements Era { +public enum IsoEra implements Era { /** - * The singleton instance for the era BCE, 'Before Current Era'. - * The 'ISO' part of the name emphasizes that this differs from the BCE - * era in the Gregorian calendar system. - * This has the numeric value of {@code 0}. + * The singleton instance for the era before the current one, 'Before Current Era', + * which has the numeric value 0. */ BCE, /** - * The singleton instance for the era CE, 'Current Era'. - * The 'ISO' part of the name emphasizes that this differs from the CE - * era in the Gregorian calendar system. - * This has the numeric value of {@code 1}. + * The singleton instance for the current era, 'Current Era', + * which has the numeric value 1. */ CE; @@ -104,18 +122,18 @@ * {@code IsoEra} is an enum representing the ISO eras of BCE/CE. * This factory allows the enum to be obtained from the {@code int} value. * - * @param era the BCE/CE value to represent, from 0 (BCE) to 1 (CE) + * @param isoEra the BCE/CE value to represent, from 0 (BCE) to 1 (CE) * @return the era singleton, not null * @throws DateTimeException if the value is invalid */ - public static IsoEra of(int era) { - switch (era) { + public static IsoEra of(int isoEra) { + switch (isoEra) { case 0: return BCE; case 1: return CE; default: - throw new DateTimeException("Invalid era: " + era); + throw new DateTimeException("Invalid era: " + isoEra); } } @@ -132,21 +150,4 @@ return ordinal(); } - @Override - public IsoChronology getChronology() { - return IsoChronology.INSTANCE; - } - - // JDK8 default methods: - //----------------------------------------------------------------------- - @Override - public LocalDate date(int year, int month, int day) { - return getChronology().date(this, year, month, day); - } - - @Override - public LocalDate dateYearDay(int year, int dayOfYear) { - return getChronology().dateYearDay(this, year, dayOfYear); - } - } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/JapaneseChronology.java --- a/src/share/classes/java/time/chrono/JapaneseChronology.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/JapaneseChronology.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,11 +61,11 @@ import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDate; +import java.time.Year; +import java.time.ZoneId; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.ValueRange; -import java.time.Year; -import java.time.ZoneId; import java.util.Arrays; import java.util.Calendar; import java.util.List; @@ -105,31 +105,6 @@ public static final JapaneseChronology INSTANCE = new JapaneseChronology(); /** - * The singleton instance for the before Meiji era ( - 1868-09-07) - * which has the value -999. - */ - public static final Era ERA_SEIREKI = JapaneseEra.SEIREKI; - /** - * The singleton instance for the Meiji era (1868-09-08 - 1912-07-29) - * which has the value -1. - */ - public static final Era ERA_MEIJI = JapaneseEra.MEIJI; - /** - * The singleton instance for the Taisho era (1912-07-30 - 1926-12-24) - * which has the value 0. - */ - public static final Era ERA_TAISHO = JapaneseEra.TAISHO; - /** - * The singleton instance for the Showa era (1926-12-25 - 1989-01-07) - * which has the value 1. - */ - public static final Era ERA_SHOWA = JapaneseEra.SHOWA; - /** - * The singleton instance for the Heisei era (1989-01-08 - current) - * which has the value 2. - */ - public static final Era ERA_HEISEI = JapaneseEra.HEISEI; - /** * Serialization version. */ private static final long serialVersionUID = 459996390165777884L; @@ -141,15 +116,6 @@ private JapaneseChronology() { } - /** - * Resolve singleton. - * - * @return the singleton instance, not null - */ - private Object readResolve() { - return INSTANCE; - } - //----------------------------------------------------------------------- /** * Gets the ID of the chronology - 'Japanese'. @@ -183,36 +149,82 @@ } //----------------------------------------------------------------------- + /** + * Obtains a local date in Japanese calendar system from the + * era, year-of-era, month-of-year and day-of-month fields. + * + * @param era the Japanese era, not null + * @param yearOfEra the year-of-era + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Japanese local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code JapaneseEra} + */ @Override public JapaneseDate date(Era era, int yearOfEra, int month, int dayOfMonth) { if (era instanceof JapaneseEra == false) { - throw new DateTimeException("Era must be JapaneseEra"); + throw new ClassCastException("Era must be JapaneseEra"); } return JapaneseDate.of((JapaneseEra) era, yearOfEra, month, dayOfMonth); } + /** + * Obtains a local date in Japanese calendar system from the + * proleptic-year, month-of-year and day-of-month fields. + * + * @param prolepticYear the proleptic-year + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Japanese local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public JapaneseDate date(int prolepticYear, int month, int dayOfMonth) { return new JapaneseDate(LocalDate.of(prolepticYear, month, dayOfMonth)); } + /** + * Obtains a local date in Japanese calendar system from the + * era, year-of-era and day-of-year fields. + * + * @param era the Japanese era, not null + * @param yearOfEra the year-of-era + * @param dayOfYear the day-of-year + * @return the Japanese local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code JapaneseEra} + */ + @Override + public JapaneseDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + } + + /** + * Obtains a local date in Japanese calendar system from the + * proleptic-year and day-of-year fields. + * + * @param prolepticYear the proleptic-year + * @param dayOfYear the day-of-year + * @return the Japanese local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public JapaneseDate dateYearDay(int prolepticYear, int dayOfYear) { LocalDate date = LocalDate.ofYearDay(prolepticYear, dayOfYear); return date(prolepticYear, date.getMonthValue(), date.getDayOfMonth()); } - @Override - public JapaneseDate date(TemporalAccessor temporal) { - if (temporal instanceof JapaneseDate) { - return (JapaneseDate) temporal; - } - return new JapaneseDate(LocalDate.from(temporal)); - } - - @Override - public JapaneseDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { - return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + /** + * Obtains a local date in the Japanese calendar system from the epoch-day. + * + * @param epochDay the epoch day + * @return the Japanese local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public JapaneseDate dateEpochDay(long epochDay) { + return new JapaneseDate(LocalDate.ofEpochDay(epochDay)); } @Override @@ -231,6 +243,14 @@ } @Override + public JapaneseDate date(TemporalAccessor temporal) { + if (temporal instanceof JapaneseDate) { + return (JapaneseDate) temporal; + } + return new JapaneseDate(LocalDate.from(temporal)); + } + + @Override public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { return (ChronoLocalDateTime)super.localDateTime(temporal); } @@ -264,7 +284,7 @@ @Override public int prolepticYear(Era era, int yearOfEra) { if (era instanceof JapaneseEra == false) { - throw new DateTimeException("Era must be JapaneseEra"); + throw new ClassCastException("Era must be JapaneseEra"); } JapaneseEra jera = (JapaneseEra) era; int gregorianYear = jera.getPrivateEra().getSinceDate().getYear() + yearOfEra - 1; @@ -273,20 +293,23 @@ } LocalGregorianCalendar.Date jdate = JCAL.newCalendarDate(null); jdate.setEra(jera.getPrivateEra()).setDate(yearOfEra, 1, 1); + if (!JapaneseChronology.JCAL.validate(jdate)) { + throw new DateTimeException("Invalid yearOfEra value"); + } JCAL.normalize(jdate); if (jdate.getNormalizedYear() == gregorianYear) { return gregorianYear; } - throw new DateTimeException("invalid yearOfEra value"); + throw new DateTimeException("Invalid yearOfEra value"); } /** * Returns the calendar system era object from the given numeric value. * * See the description of each Era for the numeric values of: - * {@link #ERA_HEISEI}, {@link #ERA_SHOWA},{@link #ERA_TAISHO}, - * {@link #ERA_MEIJI}), only Meiji and later eras are supported. - * Prior to Meiji {@link #ERA_SEIREKI} is used. + * {@link JapaneseEra#HEISEI}, {@link JapaneseEra#SHOWA},{@link JapaneseEra#TAISHO}, + * {@link JapaneseEra#MEIJI}), only Meiji and later eras are supported. + * Prior to Meiji {@link JapaneseEra#SEIREKI} is used. * * @param eraValue the era value * @return the Japanese {@code Era} for the given numeric era value @@ -299,7 +322,7 @@ @Override public List eras() { - return Arrays.asList(JapaneseEra.values()); + return Arrays.asList(JapaneseEra.values()); } //----------------------------------------------------------------------- @@ -322,20 +345,24 @@ case NANO_OF_SECOND: case CLOCK_HOUR_OF_DAY: case CLOCK_HOUR_OF_AMPM: - case EPOCH_DAY: - case EPOCH_MONTH: + case EPOCH_DAY: // TODO: if year is restricted, then so is epoch-day return field.range(); } Calendar jcal = Calendar.getInstance(LOCALE); int fieldIndex; switch (field) { case ERA: - return ValueRange.of(jcal.getMinimum(Calendar.ERA) - JapaneseEra.ERA_OFFSET, + return ValueRange.of(JapaneseEra.SEIREKI.getValue(), jcal.getMaximum(Calendar.ERA) - JapaneseEra.ERA_OFFSET); case YEAR: case YEAR_OF_ERA: + // TODO: this is not right return ValueRange.of(Year.MIN_VALUE, jcal.getGreatestMinimum(Calendar.YEAR), jcal.getLeastMaximum(Calendar.YEAR), Year.MAX_VALUE); + case PROLEPTIC_MONTH: + // TODO: should be the range of months bound by the valid range of years + return ValueRange.of((jcal.getGreatestMinimum(Calendar.YEAR) - 1) * 12, + (jcal.getLeastMaximum(Calendar.YEAR)) * 12); case MONTH_OF_YEAR: return ValueRange.of(jcal.getMinimum(Calendar.MONTH) + 1, jcal.getGreatestMinimum(Calendar.MONTH) + 1, jcal.getLeastMaximum(Calendar.MONTH) + 1, jcal.getMaximum(Calendar.MONTH) + 1); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/JapaneseDate.java --- a/src/share/classes/java/time/chrono/JapaneseDate.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/JapaneseDate.java Sat Apr 13 21:51:36 2013 +0100 @@ -69,14 +69,16 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.Period; +import java.time.Year; import java.time.ZoneId; import java.time.temporal.ChronoField; -import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Calendar; import java.util.Objects; @@ -191,7 +193,7 @@ */ public static JapaneseDate of(Era era, int yearOfEra, int month, int dayOfMonth) { if (era instanceof JapaneseEra == false) { - throw new DateTimeException("Era must be JapaneseEra"); + throw new ClassCastException("Era must be JapaneseEra"); } return JapaneseDate.of((JapaneseEra) era, yearOfEra, month, dayOfMonth); } @@ -252,7 +254,7 @@ LocalGregorianCalendar.Date jdate = JapaneseChronology.JCAL.newCalendarDate(null); jdate.setEra(era.getPrivateEra()).setDate(yearOfEra, month, dayOfMonth); if (!JapaneseChronology.JCAL.validate(jdate)) { - throw new IllegalArgumentException(); + throw new DateTimeException("year, month, and day not valid for Era"); } LocalDate date = LocalDate.of(jdate.getNormalizedYear(), month, dayOfMonth); return new JapaneseDate(era, yearOfEra, date); @@ -307,22 +309,54 @@ } //----------------------------------------------------------------------- + /** + * Gets the chronology of this date, which is the Japanese calendar system. + *

    + * The {@code Chronology} represents the calendar system in use. + * The era and other fields in {@link ChronoField} are defined by the chronology. + * + * @return the Japanese chronology, not null + */ @Override public JapaneseChronology getChronology() { return JapaneseChronology.INSTANCE; } + /** + * Gets the era applicable at this date. + *

    + * The Japanese calendar system has multiple eras defined by {@link JapaneseEra}. + * + * @return the era applicable at this date, not null + */ + @Override + public JapaneseEra getEra() { + return era; + } + + /** + * Returns the length of the month represented by this date. + *

    + * This returns the length of the month in days. + * Month lengths match those of the ISO calendar system. + * + * @return the length of the month in days + */ @Override public int lengthOfMonth() { return isoDate.lengthOfMonth(); } + //----------------------------------------------------------------------- @Override public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { if (isSupported(field)) { ChronoField f = (ChronoField) field; switch (f) { + case DAY_OF_MONTH: + case ALIGNED_WEEK_OF_MONTH: + return isoDate.range(field); case DAY_OF_YEAR: return actualRange(Calendar.DAY_OF_YEAR); case YEAR_OF_ERA: @@ -330,14 +364,14 @@ } return getChronology().range(f); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.rangeRefinedBy(this); } private ValueRange actualRange(int calendarField) { Calendar jcal = Calendar.getInstance(JapaneseChronology.LOCALE); - jcal.set(Calendar.ERA, era.getValue() + JapaneseEra.ERA_OFFSET); + jcal.set(Calendar.ERA, era.getValue() + JapaneseEra.ERA_OFFSET); // TODO: cannot calculate this way for SEIREKI jcal.set(yearOfEra, isoDate.getMonthValue() - 1, isoDate.getDayOfMonth()); return ValueRange.of(jcal.getActualMinimum(calendarField), jcal.getActualMaximum(calendarField)); @@ -346,6 +380,12 @@ @Override public long getLong(TemporalField field) { if (field instanceof ChronoField) { + // same as ISO: + // DAY_OF_WEEK, ALIGNED_DAY_OF_WEEK_IN_MONTH, DAY_OF_MONTH, EPOCH_DAY, + // ALIGNED_WEEK_OF_MONTH, MONTH_OF_YEAR, PROLEPTIC_MONTH, YEAR + // + // calendar specific fields + // ALIGNED_DAY_OF_WEEK_IN_YEAR, DAY_OF_YEAR, ALIGNED_WEEK_OF_YEAR, YEAR_OF_ERA, ERA switch ((ChronoField) field) { case YEAR_OF_ERA: return yearOfEra; @@ -355,8 +395,8 @@ LocalGregorianCalendar.Date jdate = toPrivateJapaneseDate(isoDate); return JapaneseChronology.JCAL.getDayOfYear(jdate); } + // TODO: ALIGNED_DAY_OF_WEEK_IN_YEAR and ALIGNED_WEEK_OF_YEAR ??? } - // TODO: review other fields return isoDate.getLong(field); } return field.getFrom(this); @@ -392,8 +432,7 @@ case YEAR_OF_ERA: case YEAR: case ERA: { - f.checkValidValue(newValue); - int nvalue = (int) newValue; + int nvalue = getChronology().range(f).checkValidIntValue(newValue, f); switch (f) { case YEAR_OF_ERA: return this.withYear(nvalue); @@ -405,15 +444,11 @@ } } } + // YEAR, PROLEPTIC_MONTH and others are same as ISO // TODO: review other fields, such as WEEK_OF_YEAR return with(isoDate.with(field, newValue)); } - return (JapaneseDate) ChronoLocalDate.super.with(field, newValue); - } - - @Override - public Era getEra() { - return era; + return ChronoLocalDate.super.with(field, newValue); } /** @@ -423,7 +458,7 @@ */ @Override public JapaneseDate with(TemporalAdjuster adjuster) { - return (JapaneseDate)super.with(adjuster); + return super.with(adjuster); } /** @@ -433,7 +468,7 @@ */ @Override public JapaneseDate plus(TemporalAmount amount) { - return (JapaneseDate)super.plus(amount); + return super.plus(amount); } /** @@ -443,7 +478,7 @@ */ @Override public JapaneseDate minus(TemporalAmount amount) { - return (JapaneseDate)super.minus(amount); + return super.minus(amount); } //----------------------------------------------------------------------- /** @@ -479,7 +514,7 @@ * @throws DateTimeException if {@code year} is invalid */ private JapaneseDate withYear(int year) { - return withYear((JapaneseEra) getEra(), year); + return withYear(getEra(), year); } //----------------------------------------------------------------------- @@ -505,32 +540,32 @@ @Override public JapaneseDate plus(long amountToAdd, TemporalUnit unit) { - return (JapaneseDate)super.plus(amountToAdd, unit); + return super.plus(amountToAdd, unit); } @Override public JapaneseDate minus(long amountToAdd, TemporalUnit unit) { - return (JapaneseDate)super.minus(amountToAdd, unit); + return super.minus(amountToAdd, unit); } @Override JapaneseDate minusYears(long yearsToSubtract) { - return (JapaneseDate)super.minusYears(yearsToSubtract); + return super.minusYears(yearsToSubtract); } @Override JapaneseDate minusMonths(long monthsToSubtract) { - return (JapaneseDate)super.minusMonths(monthsToSubtract); + return super.minusMonths(monthsToSubtract); } @Override JapaneseDate minusWeeks(long weeksToSubtract) { - return (JapaneseDate)super.minusWeeks(weeksToSubtract); + return super.minusWeeks(weeksToSubtract); } @Override JapaneseDate minusDays(long daysToSubtract) { - return (JapaneseDate)super.minusDays(daysToSubtract); + return super.minusDays(daysToSubtract); } private JapaneseDate with(LocalDate newDate) { @@ -539,7 +574,7 @@ @Override // for javadoc and covariant return type public final ChronoLocalDateTime atTime(LocalTime localTime) { - return (ChronoLocalDateTime)super.atTime(localTime); + return super.atTime(localTime); } @Override diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/JapaneseEra.java --- a/src/share/classes/java/time/chrono/JapaneseEra.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/JapaneseEra.java Sat Apr 13 21:51:36 2013 +0100 @@ -24,6 +24,11 @@ */ /* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos * * All rights reserved. @@ -56,6 +61,8 @@ */ package java.time.chrono; +import static java.time.temporal.ChronoField.ERA; + import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -64,7 +71,12 @@ import java.io.Serializable; import java.time.DateTimeException; import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalField; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.time.temporal.ValueRange; import java.util.Arrays; +import java.util.Objects; import sun.util.calendar.CalendarDate; @@ -84,7 +96,7 @@ * * @since 1.8 */ -final class JapaneseEra +public final class JapaneseEra implements Era, Serializable { // The offset value to 0-based index from the era value. @@ -202,7 +214,7 @@ //----------------------------------------------------------------------- /** - * Obtains an instance of {@code JapaneseEra} from a value. + * Obtains an instance of {@code JapaneseEra} from an {@code int} value. *

    * The {@link #SHOWA} era that contains 1970-01-01 (ISO calendar system) has the value 1 * Later era is numbered 2 ({@link #HEISEI}). Earlier eras are numbered 0 ({@link #TAISHO}), @@ -210,22 +222,49 @@ * {@link #SEIREKI} is used. * * @param japaneseEra the era to represent - * @return the {@code JapaneseEra} singleton, never null - * @throws DateTimeException if {@code japaneseEra} is invalid + * @return the {@code JapaneseEra} singleton, not null + * @throws DateTimeException if the value is invalid */ public static JapaneseEra of(int japaneseEra) { if (japaneseEra != SEIREKI.eraValue && (japaneseEra < MEIJI.eraValue || japaneseEra > HEISEI.eraValue)) { - throw new DateTimeException("japaneseEra is invalid"); + throw new DateTimeException("Invalid era: " + japaneseEra); } return KNOWN_ERAS[ordinal(japaneseEra)]; } /** + * Returns the {@code JapaneseEra} with the name. + *

    + * The string must match exactly the name of the era. + * (Extraneous whitespace characters are not permitted.) + * + * @param japaneseEra the japaneseEra name; non-null + * @return the {@code JapaneseEra} singleton, never null + * @throws IllegalArgumentException if there is not JapaneseEra with the specified name + */ + public static JapaneseEra valueOf(String japaneseEra) { + Objects.requireNonNull(japaneseEra, "japaneseEra"); + for (JapaneseEra era : KNOWN_ERAS) { + if (era.getName().equals(japaneseEra)) { + return era; + } + } + throw new IllegalArgumentException("japaneseEra is invalid"); + } + + /** * Returns an array of JapaneseEras. + *

    + * This method may be used to iterate over the JapaneseEras as follows: + *

    +     * for (JapaneseEra c : JapaneseEra.values())
    +     *     System.out.println(c);
    +     * 
    + * * @return an array of JapaneseEras */ - static JapaneseEra[] values() { + public static JapaneseEra[] values() { return Arrays.copyOf(KNOWN_ERAS, KNOWN_ERAS.length); } @@ -268,16 +307,17 @@ /** * Returns the index into the arrays from the Era value. * the eraValue is a valid Era number, -999, -1..2. - * @param eravalue the era value to convert to the index + * + * @param eraValue the era value to convert to the index * @return the index of the current Era */ - private static int ordinal(int eravalue) { - return (eravalue == SEIREKI.eraValue) ? 0 : eravalue + ERA_OFFSET; + private static int ordinal(int eraValue) { + return (eraValue == SEIREKI.eraValue) ? 0 : eraValue + ERA_OFFSET; } //----------------------------------------------------------------------- /** - * Returns the numeric value of this {@code JapaneseEra}. + * Gets the numeric era {@code int} value. *

    * The {@link #SHOWA} era that contains 1970-01-01 (ISO calendar system) has the value 1. * Later eras are numbered from 2 ({@link #HEISEI}). @@ -290,9 +330,38 @@ return eraValue; } - @Override - public JapaneseChronology getChronology() { - return JapaneseChronology.INSTANCE; + //----------------------------------------------------------------------- + /** + * Gets the range of valid values for the specified field. + *

    + * The range object expresses the minimum and maximum valid values for a field. + * This era is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

    + * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code ERA} field returns the range. + * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. + *

    + * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + *

    + * The range of valid Japanese eras can change over time due to the nature + * of the Japanese calendar system. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the unit is not supported + */ + @Override // override as super would return range from 0 to 1 + public ValueRange range(TemporalField field) { + if (field == ERA) { + return JapaneseChronology.INSTANCE.range(ERA); + } + return Era.super.range(field); } //----------------------------------------------------------------------- diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/MinguoChronology.java --- a/src/share/classes/java/time/chrono/MinguoChronology.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/MinguoChronology.java Sat Apr 13 21:51:36 2013 +0100 @@ -56,6 +56,7 @@ */ package java.time.chrono; +import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; import static java.time.temporal.ChronoField.YEAR; import java.io.Serializable; @@ -107,16 +108,6 @@ public static final MinguoChronology INSTANCE = new MinguoChronology(); /** - * The singleton instance for the era ROC. - */ - public static final Era ERA_ROC = MinguoEra.ROC; - - /** - * The singleton instance for the era BEFORE_ROC. - */ - public static final Era ERA_BEFORE_ROC = MinguoEra.BEFORE_ROC; - - /** * Serialization version. */ private static final long serialVersionUID = 1039765215346859963L; @@ -131,15 +122,6 @@ private MinguoChronology() { } - /** - * Resolve singleton. - * - * @return the singleton instance, not null - */ - private Object readResolve() { - return INSTANCE; - } - //----------------------------------------------------------------------- /** * Gets the ID of the chronology - 'Minguo'. @@ -173,32 +155,78 @@ } //----------------------------------------------------------------------- + /** + * Obtains a local date in Minguo calendar system from the + * era, year-of-era, month-of-year and day-of-month fields. + * + * @param era the Minguo era, not null + * @param yearOfEra the year-of-era + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Minguo local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code MinguoEra} + */ + @Override + public MinguoDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + return date(prolepticYear(era, yearOfEra), month, dayOfMonth); + } + + /** + * Obtains a local date in Minguo calendar system from the + * proleptic-year, month-of-year and day-of-month fields. + * + * @param prolepticYear the proleptic-year + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Minguo local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public MinguoDate date(int prolepticYear, int month, int dayOfMonth) { return new MinguoDate(LocalDate.of(prolepticYear + YEARS_DIFFERENCE, month, dayOfMonth)); } + /** + * Obtains a local date in Minguo calendar system from the + * era, year-of-era and day-of-year fields. + * + * @param era the Minguo era, not null + * @param yearOfEra the year-of-era + * @param dayOfYear the day-of-year + * @return the Minguo local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code MinguoEra} + */ + @Override + public MinguoDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + } + + /** + * Obtains a local date in Minguo calendar system from the + * proleptic-year and day-of-year fields. + * + * @param prolepticYear the proleptic-year + * @param dayOfYear the day-of-year + * @return the Minguo local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public MinguoDate dateYearDay(int prolepticYear, int dayOfYear) { return new MinguoDate(LocalDate.ofYearDay(prolepticYear + YEARS_DIFFERENCE, dayOfYear)); } - @Override - public MinguoDate date(TemporalAccessor temporal) { - if (temporal instanceof MinguoDate) { - return (MinguoDate) temporal; - } - return new MinguoDate(LocalDate.from(temporal)); - } - @Override - public MinguoDate date(Era era, int yearOfEra, int month, int dayOfMonth) { - return date(prolepticYear(era, yearOfEra), month, dayOfMonth); - - } - - @Override - public MinguoDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { - return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + /** + * Obtains a local date in the Minguo calendar system from the epoch-day. + * + * @param epochDay the epoch day + * @return the Minguo local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public MinguoDate dateEpochDay(long epochDay) { + return new MinguoDate(LocalDate.ofEpochDay(epochDay)); } @Override @@ -217,6 +245,14 @@ } @Override + public MinguoDate date(TemporalAccessor temporal) { + if (temporal instanceof MinguoDate) { + return (MinguoDate) temporal; + } + return new MinguoDate(LocalDate.from(temporal)); + } + + @Override public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { return (ChronoLocalDateTime)super.localDateTime(temporal); } @@ -250,7 +286,7 @@ @Override public int prolepticYear(Era era, int yearOfEra) { if (era instanceof MinguoEra == false) { - throw new DateTimeException("Era must be MinguoEra"); + throw new ClassCastException("Era must be MinguoEra"); } return (era == MinguoEra.ROC ? yearOfEra : 1 - yearOfEra); } @@ -269,6 +305,10 @@ @Override public ValueRange range(ChronoField field) { switch (field) { + case PROLEPTIC_MONTH: { + ValueRange range = PROLEPTIC_MONTH.range(); + return ValueRange.of(range.getMinimum() - YEARS_DIFFERENCE * 12L, range.getMaximum() - YEARS_DIFFERENCE * 12L); + } case YEAR_OF_ERA: { ValueRange range = YEAR.range(); return ValueRange.of(1, range.getMaximum() - YEARS_DIFFERENCE, -range.getMinimum() + 1 + YEARS_DIFFERENCE); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/MinguoDate.java --- a/src/share/classes/java/time/chrono/MinguoDate.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/MinguoDate.java Sat Apr 13 21:51:36 2013 +0100 @@ -72,12 +72,13 @@ import java.time.Period; import java.time.ZoneId; import java.time.temporal.ChronoField; -import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; @@ -205,16 +206,46 @@ } //----------------------------------------------------------------------- + /** + * Gets the chronology of this date, which is the Minguo calendar system. + *

    + * The {@code Chronology} represents the calendar system in use. + * The era and other fields in {@link ChronoField} are defined by the chronology. + * + * @return the Minguo chronology, not null + */ @Override public MinguoChronology getChronology() { return MinguoChronology.INSTANCE; } + /** + * Gets the era applicable at this date. + *

    + * The Minguo calendar system has two eras, 'ROC' and 'BEFORE_ROC', + * defined by {@link MinguoEra}. + * + * @return the era applicable at this date, not null + */ + @Override + public MinguoEra getEra() { + return (getProlepticYear() >= 1 ? MinguoEra.ROC : MinguoEra.BEFORE_ROC); + } + + /** + * Returns the length of the month represented by this date. + *

    + * This returns the length of the month in days. + * Month lengths match those of the ISO calendar system. + * + * @return the length of the month in days + */ @Override public int lengthOfMonth() { return isoDate.lengthOfMonth(); } + //----------------------------------------------------------------------- @Override public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { @@ -233,7 +264,7 @@ } return getChronology().range(f); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.rangeRefinedBy(this); } @@ -242,6 +273,8 @@ public long getLong(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { + case PROLEPTIC_MONTH: + return getProlepticMonth(); case YEAR_OF_ERA: { int prolepticYear = getProlepticYear(); return (prolepticYear >= 1 ? prolepticYear : 1 - prolepticYear); @@ -256,6 +289,10 @@ return field.getFrom(this); } + private long getProlepticMonth() { + return getProlepticYear() * 12L + isoDate.getMonthValue() - 1; + } + private int getProlepticYear() { return isoDate.getYear() - YEARS_DIFFERENCE; } @@ -269,11 +306,13 @@ return this; } switch (f) { + case PROLEPTIC_MONTH: + getChronology().range(f).checkValidValue(newValue, f); + return plusMonths(newValue - getProlepticMonth()); case YEAR_OF_ERA: case YEAR: case ERA: { - f.checkValidValue(newValue); - int nvalue = (int) newValue; + int nvalue = getChronology().range(f).checkValidIntValue(newValue, f); switch (f) { case YEAR_OF_ERA: return with(isoDate.withYear(getProlepticYear() >= 1 ? nvalue + YEARS_DIFFERENCE : (1 - nvalue) + YEARS_DIFFERENCE)); @@ -286,7 +325,7 @@ } return with(isoDate.with(field, newValue)); } - return (MinguoDate) ChronoLocalDate.super.with(field, newValue); + return ChronoLocalDate.super.with(field, newValue); } /** @@ -296,7 +335,7 @@ */ @Override public MinguoDate with(TemporalAdjuster adjuster) { - return (MinguoDate)super.with(adjuster); + return super.with(adjuster); } /** @@ -306,7 +345,7 @@ */ @Override public MinguoDate plus(TemporalAmount amount) { - return (MinguoDate)super.plus(amount); + return super.plus(amount); } /** @@ -316,7 +355,7 @@ */ @Override public MinguoDate minus(TemporalAmount amount) { - return (MinguoDate)super.minus(amount); + return super.minus(amount); } //----------------------------------------------------------------------- @@ -337,37 +376,37 @@ @Override public MinguoDate plus(long amountToAdd, TemporalUnit unit) { - return (MinguoDate)super.plus(amountToAdd, unit); + return super.plus(amountToAdd, unit); } @Override public MinguoDate minus(long amountToAdd, TemporalUnit unit) { - return (MinguoDate)super.minus(amountToAdd, unit); + return super.minus(amountToAdd, unit); } @Override MinguoDate plusWeeks(long weeksToAdd) { - return (MinguoDate)super.plusWeeks(weeksToAdd); + return super.plusWeeks(weeksToAdd); } @Override MinguoDate minusYears(long yearsToSubtract) { - return (MinguoDate)super.minusYears(yearsToSubtract); + return super.minusYears(yearsToSubtract); } @Override MinguoDate minusMonths(long monthsToSubtract) { - return (MinguoDate)super.minusMonths(monthsToSubtract); + return super.minusMonths(monthsToSubtract); } @Override MinguoDate minusWeeks(long weeksToSubtract) { - return (MinguoDate)super.minusWeeks(weeksToSubtract); + return super.minusWeeks(weeksToSubtract); } @Override MinguoDate minusDays(long daysToSubtract) { - return (MinguoDate)super.minusDays(daysToSubtract); + return super.minusDays(daysToSubtract); } private MinguoDate with(LocalDate newDate) { @@ -376,7 +415,7 @@ @Override // for javadoc and covariant return type public final ChronoLocalDateTime atTime(LocalTime localTime) { - return (ChronoLocalDateTime)super.atTime(localTime); + return super.atTime(localTime); } @Override @@ -419,7 +458,7 @@ out.writeByte(get(DAY_OF_MONTH)); } - static ChronoLocalDate readExternal(DataInput in) throws IOException { + static ChronoLocalDate readExternal(DataInput in) throws IOException { int year = in.readInt(); int month = in.readByte(); int dayOfMonth = in.readByte(); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/MinguoEra.java --- a/src/share/classes/java/time/chrono/MinguoEra.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/MinguoEra.java Sat Apr 13 21:51:36 2013 +0100 @@ -24,6 +24,11 @@ */ /* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos * * All rights reserved. @@ -65,7 +70,34 @@ * An era in the Minguo calendar system. *

    * The Minguo calendar system has two eras. - * The date {@code 0001-01-01 (Minguo)} is equal to {@code 1912-01-01 (ISO)}. + * The current era, for years from 1 onwards, is known as the 'Republic of China' era. + * All previous years, zero or earlier in the proleptic count or one and greater + * in the year-of-era count, are part of the 'Before Republic of China' era. + *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    year-of-eraeraproleptic-yearISO proleptic-year
    2ROC21913
    1ROC11912
    1BEFORE_ROC01911
    2BEFORE_ROC-11910
    *

    * Do not use {@code ordinal()} to obtain the numeric representation of {@code MinguoEra}. * Use {@code getValue()} instead. @@ -75,16 +107,16 @@ * * @since 1.8 */ -enum MinguoEra implements Era { +public enum MinguoEra implements Era { /** - * The singleton instance for the era BEFORE_ROC, 'Before Republic of China'. - * This has the numeric value of {@code 0}. + * The singleton instance for the era before the current one, 'Before Republic of China Era', + * which has the numeric value 0. */ BEFORE_ROC, /** - * The singleton instance for the era ROC, 'Republic of China'. - * This has the numeric value of {@code 1}. + * The singleton instance for the current era, 'Republic of China Era', + * which has the numeric value 1. */ ROC; @@ -95,18 +127,18 @@ * {@code MinguoEra} is an enum representing the Minguo eras of BEFORE_ROC/ROC. * This factory allows the enum to be obtained from the {@code int} value. * - * @param era the BEFORE_ROC/ROC value to represent, from 0 (BEFORE_ROC) to 1 (ROC) + * @param minguoEra the BEFORE_ROC/ROC value to represent, from 0 (BEFORE_ROC) to 1 (ROC) * @return the era singleton, not null * @throws DateTimeException if the value is invalid */ - public static MinguoEra of(int era) { - switch (era) { + public static MinguoEra of(int minguoEra) { + switch (minguoEra) { case 0: return BEFORE_ROC; case 1: return ROC; default: - throw new DateTimeException("Invalid era: " + era); + throw new DateTimeException("Invalid era: " + minguoEra); } } @@ -123,24 +155,7 @@ return ordinal(); } - @Override - public MinguoChronology getChronology() { - return MinguoChronology.INSTANCE; - } - - // JDK8 default methods: //----------------------------------------------------------------------- - @Override - public MinguoDate date(int year, int month, int day) { - return (MinguoDate)(getChronology().date(this, year, month, day)); - } - - @Override - public MinguoDate dateYearDay(int year, int dayOfYear) { - return (MinguoDate)(getChronology().dateYearDay(this, year, dayOfYear)); - } - - //------------------------------------------------------------------------- private Object writeReplace() { return new Ser(Ser.MINGUO_ERA_TYPE, this); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ThaiBuddhistChronology.java --- a/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ThaiBuddhistChronology.java Sat Apr 13 21:51:36 2013 +0100 @@ -56,6 +56,7 @@ */ package java.time.chrono; +import static java.time.temporal.ChronoField.PROLEPTIC_MONTH; import static java.time.temporal.ChronoField.YEAR; import java.io.Serializable; @@ -106,15 +107,6 @@ * Singleton instance of the Buddhist chronology. */ public static final ThaiBuddhistChronology INSTANCE = new ThaiBuddhistChronology(); - /** - * The singleton instance for the era before the current one - Before Buddhist - - * which has the value 0. - */ - public static final Era ERA_BEFORE_BE = ThaiBuddhistEra.BEFORE_BE; - /** - * The singleton instance for the current era - Buddhist - which has the value 1. - */ - public static final Era ERA_BE = ThaiBuddhistEra.BE; /** * Serialization version. @@ -166,15 +158,6 @@ private ThaiBuddhistChronology() { } - /** - * Resolve singleton. - * - * @return the singleton instance, not null - */ - private Object readResolve() { - return INSTANCE; - } - //----------------------------------------------------------------------- /** * Gets the ID of the chronology - 'ThaiBuddhist'. @@ -208,32 +191,78 @@ } //----------------------------------------------------------------------- + /** + * Obtains a local date in Thai Buddhist calendar system from the + * era, year-of-era, month-of-year and day-of-month fields. + * + * @param era the Thai Buddhist era, not null + * @param yearOfEra the year-of-era + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Thai Buddhist local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code ThaiBuddhistEra} + */ + @Override + public ThaiBuddhistDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + return date(prolepticYear(era, yearOfEra), month, dayOfMonth); + } + + /** + * Obtains a local date in Thai Buddhist calendar system from the + * proleptic-year, month-of-year and day-of-month fields. + * + * @param prolepticYear the proleptic-year + * @param month the month-of-year + * @param dayOfMonth the day-of-month + * @return the Thai Buddhist local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public ThaiBuddhistDate date(int prolepticYear, int month, int dayOfMonth) { return new ThaiBuddhistDate(LocalDate.of(prolepticYear - YEARS_DIFFERENCE, month, dayOfMonth)); } + /** + * Obtains a local date in Thai Buddhist calendar system from the + * era, year-of-era and day-of-year fields. + * + * @param era the Thai Buddhist era, not null + * @param yearOfEra the year-of-era + * @param dayOfYear the day-of-year + * @return the Thai Buddhist local date, not null + * @throws DateTimeException if unable to create the date + * @throws ClassCastException if the {@code era} is not a {@code ThaiBuddhistEra} + */ + @Override + public ThaiBuddhistDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + } + + /** + * Obtains a local date in Thai Buddhist calendar system from the + * proleptic-year and day-of-year fields. + * + * @param prolepticYear the proleptic-year + * @param dayOfYear the day-of-year + * @return the Thai Buddhist local date, not null + * @throws DateTimeException if unable to create the date + */ @Override public ThaiBuddhistDate dateYearDay(int prolepticYear, int dayOfYear) { return new ThaiBuddhistDate(LocalDate.ofYearDay(prolepticYear - YEARS_DIFFERENCE, dayOfYear)); } - @Override - public ThaiBuddhistDate date(TemporalAccessor temporal) { - if (temporal instanceof ThaiBuddhistDate) { - return (ThaiBuddhistDate) temporal; - } - return new ThaiBuddhistDate(LocalDate.from(temporal)); - } - @Override - public ThaiBuddhistDate date(Era era, int yearOfEra, int month, int dayOfMonth) { - return date(prolepticYear(era, yearOfEra), month, dayOfMonth); - - } - - @Override - public ThaiBuddhistDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { - return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + /** + * Obtains a local date in the Thai Buddhist calendar system from the epoch-day. + * + * @param epochDay the epoch day + * @return the Thai Buddhist local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public ThaiBuddhistDate dateEpochDay(long epochDay) { + return new ThaiBuddhistDate(LocalDate.ofEpochDay(epochDay)); } @Override @@ -252,6 +281,14 @@ } @Override + public ThaiBuddhistDate date(TemporalAccessor temporal) { + if (temporal instanceof ThaiBuddhistDate) { + return (ThaiBuddhistDate) temporal; + } + return new ThaiBuddhistDate(LocalDate.from(temporal)); + } + + @Override public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { return (ChronoLocalDateTime)super.localDateTime(temporal); } @@ -285,7 +322,7 @@ @Override public int prolepticYear(Era era, int yearOfEra) { if (era instanceof ThaiBuddhistEra == false) { - throw new DateTimeException("Era must be BuddhistEra"); + throw new ClassCastException("Era must be BuddhistEra"); } return (era == ThaiBuddhistEra.BE ? yearOfEra : 1 - yearOfEra); } @@ -304,6 +341,10 @@ @Override public ValueRange range(ChronoField field) { switch (field) { + case PROLEPTIC_MONTH: { + ValueRange range = PROLEPTIC_MONTH.range(); + return ValueRange.of(range.getMinimum() + YEARS_DIFFERENCE * 12L, range.getMaximum() + YEARS_DIFFERENCE * 12L); + } case YEAR_OF_ERA: { ValueRange range = YEAR.range(); return ValueRange.of(1, -(range.getMinimum() + YEARS_DIFFERENCE) + 1, range.getMaximum() + YEARS_DIFFERENCE); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ThaiBuddhistDate.java --- a/src/share/classes/java/time/chrono/ThaiBuddhistDate.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ThaiBuddhistDate.java Sat Apr 13 21:51:36 2013 +0100 @@ -72,12 +72,13 @@ import java.time.Period; import java.time.ZoneId; import java.time.temporal.ChronoField; -import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAmount; import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; import java.time.temporal.TemporalUnit; +import java.time.temporal.UnsupportedTemporalTypeException; import java.time.temporal.ValueRange; import java.util.Objects; @@ -205,16 +206,46 @@ } //----------------------------------------------------------------------- + /** + * Gets the chronology of this date, which is the Thai Buddhist calendar system. + *

    + * The {@code Chronology} represents the calendar system in use. + * The era and other fields in {@link ChronoField} are defined by the chronology. + * + * @return the Thai Buddhist chronology, not null + */ @Override public ThaiBuddhistChronology getChronology() { return ThaiBuddhistChronology.INSTANCE; } + /** + * Gets the era applicable at this date. + *

    + * The Thai Buddhist calendar system has two eras, 'BE' and 'BEFORE_BE', + * defined by {@link ThaiBuddhistEra}. + * + * @return the era applicable at this date, not null + */ + @Override + public ThaiBuddhistEra getEra() { + return (getProlepticYear() >= 1 ? ThaiBuddhistEra.BE : ThaiBuddhistEra.BEFORE_BE); + } + + /** + * Returns the length of the month represented by this date. + *

    + * This returns the length of the month in days. + * Month lengths match those of the ISO calendar system. + * + * @return the length of the month in days + */ @Override public int lengthOfMonth() { return isoDate.lengthOfMonth(); } + //----------------------------------------------------------------------- @Override public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { @@ -233,7 +264,7 @@ } return getChronology().range(f); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } return field.rangeRefinedBy(this); } @@ -242,6 +273,8 @@ public long getLong(TemporalField field) { if (field instanceof ChronoField) { switch ((ChronoField) field) { + case PROLEPTIC_MONTH: + return getProlepticMonth(); case YEAR_OF_ERA: { int prolepticYear = getProlepticYear(); return (prolepticYear >= 1 ? prolepticYear : 1 - prolepticYear); @@ -256,6 +289,10 @@ return field.getFrom(this); } + private long getProlepticMonth() { + return getProlepticYear() * 12L + isoDate.getMonthValue() - 1; + } + private int getProlepticYear() { return isoDate.getYear() + YEARS_DIFFERENCE; } @@ -269,11 +306,13 @@ return this; } switch (f) { + case PROLEPTIC_MONTH: + getChronology().range(f).checkValidValue(newValue, f); + return plusMonths(newValue - getProlepticMonth()); case YEAR_OF_ERA: case YEAR: case ERA: { - f.checkValidValue(newValue); - int nvalue = (int) newValue; + int nvalue = getChronology().range(f).checkValidIntValue(newValue, f); switch (f) { case YEAR_OF_ERA: return with(isoDate.withYear((getProlepticYear() >= 1 ? nvalue : 1 - nvalue) - YEARS_DIFFERENCE)); @@ -286,7 +325,7 @@ } return with(isoDate.with(field, newValue)); } - return (ThaiBuddhistDate) ChronoLocalDate.super.with(field, newValue); + return ChronoLocalDate.super.with(field, newValue); } /** @@ -296,7 +335,7 @@ */ @Override public ThaiBuddhistDate with(TemporalAdjuster adjuster) { - return (ThaiBuddhistDate)super.with(adjuster); + return super.with(adjuster); } /** @@ -306,7 +345,7 @@ */ @Override public ThaiBuddhistDate plus(TemporalAmount amount) { - return (ThaiBuddhistDate)super.plus(amount); + return super.plus(amount); } /** @@ -316,7 +355,7 @@ */ @Override public ThaiBuddhistDate minus(TemporalAmount amount) { - return (ThaiBuddhistDate)super.minus(amount); + return super.minus(amount); } //----------------------------------------------------------------------- @@ -332,7 +371,7 @@ @Override ThaiBuddhistDate plusWeeks(long weeksToAdd) { - return (ThaiBuddhistDate)super.plusWeeks(weeksToAdd); + return super.plusWeeks(weeksToAdd); } @Override @@ -342,32 +381,32 @@ @Override public ThaiBuddhistDate plus(long amountToAdd, TemporalUnit unit) { - return (ThaiBuddhistDate)super.plus(amountToAdd, unit); + return super.plus(amountToAdd, unit); } @Override public ThaiBuddhistDate minus(long amountToAdd, TemporalUnit unit) { - return (ThaiBuddhistDate)super.minus(amountToAdd, unit); + return super.minus(amountToAdd, unit); } @Override ThaiBuddhistDate minusYears(long yearsToSubtract) { - return (ThaiBuddhistDate)super.minusYears(yearsToSubtract); + return super.minusYears(yearsToSubtract); } @Override ThaiBuddhistDate minusMonths(long monthsToSubtract) { - return (ThaiBuddhistDate)super.minusMonths(monthsToSubtract); + return super.minusMonths(monthsToSubtract); } @Override ThaiBuddhistDate minusWeeks(long weeksToSubtract) { - return (ThaiBuddhistDate)super.minusWeeks(weeksToSubtract); + return super.minusWeeks(weeksToSubtract); } @Override ThaiBuddhistDate minusDays(long daysToSubtract) { - return (ThaiBuddhistDate)super.minusDays(daysToSubtract); + return super.minusDays(daysToSubtract); } private ThaiBuddhistDate with(LocalDate newDate) { @@ -376,7 +415,7 @@ @Override // for javadoc and covariant return type public final ChronoLocalDateTime atTime(LocalTime localTime) { - return (ChronoLocalDateTime)super.atTime(localTime); + return super.atTime(localTime); } @Override diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/chrono/ThaiBuddhistEra.java --- a/src/share/classes/java/time/chrono/ThaiBuddhistEra.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/chrono/ThaiBuddhistEra.java Sat Apr 13 21:51:36 2013 +0100 @@ -24,6 +24,11 @@ */ /* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos * * All rights reserved. @@ -56,7 +61,6 @@ */ package java.time.chrono; - import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -66,37 +70,66 @@ * An era in the Thai Buddhist calendar system. *

    * The Thai Buddhist calendar system has two eras. + * The current era, for years from 1 onwards, is known as the 'Buddhist' era. + * All previous years, zero or earlier in the proleptic count or one and greater + * in the year-of-era count, are part of the 'Before Buddhist' era. *

    - * Do not use ordinal() to obtain the numeric representation of a ThaiBuddhistEra - * instance. Use getValue() instead. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    year-of-eraeraproleptic-yearISO proleptic-year
    2BE2-542
    1BE1-543
    1BEFORE_BE0-544
    2BEFORE_BE-1-545
    + *

    + * Do not use {@code ordinal()} to obtain the numeric representation of {@code ThaiBuddhistEra}. + * Use {@code getValue()} instead. * *

    Specification for implementors

    * This is an immutable and thread-safe enum. * * @since 1.8 */ -enum ThaiBuddhistEra implements Era { +public enum ThaiBuddhistEra implements Era { /** * The singleton instance for the era before the current one, 'Before Buddhist Era', - * which has the value 0. + * which has the numeric value 0. */ BEFORE_BE, /** - * The singleton instance for the current era, 'Buddhist Era', which has the value 1. + * The singleton instance for the current era, 'Buddhist Era', + * which has the numeric value 1. */ BE; //----------------------------------------------------------------------- /** - * Obtains an instance of {@code ThaiBuddhistEra} from a value. + * Obtains an instance of {@code ThaiBuddhistEra} from an {@code int} value. *

    - * The current era (from ISO year -543 onwards) has the value 1 - * The previous era has the value 0. + * {@code ThaiBuddhistEra} is an enum representing the Thai Buddhist eras of BEFORE_BE/BE. + * This factory allows the enum to be obtained from the {@code int} value. * * @param thaiBuddhistEra the era to represent, from 0 to 1 * @return the BuddhistEra singleton, never null - * @throws IllegalCalendarFieldValueException if the era is invalid + * @throws DateTimeException if the era is invalid */ public static ThaiBuddhistEra of(int thaiBuddhistEra) { switch (thaiBuddhistEra) { @@ -105,16 +138,15 @@ case 1: return BE; default: - throw new DateTimeException("Era is not valid for ThaiBuddhistEra"); + throw new DateTimeException("Invalid era: " + thaiBuddhistEra); } } //----------------------------------------------------------------------- /** - * Gets the era numeric value. + * Gets the numeric era {@code int} value. *

    - * The current era (from ISO year -543 onwards) has the value 1 - * The previous era has the value 0. + * The era BEFORE_BE has the value 0, while the era BE has the value 1. * * @return the era value, from 0 (BEFORE_BE) to 1 (BE) */ @@ -123,23 +155,6 @@ return ordinal(); } - @Override - public ThaiBuddhistChronology getChronology() { - return ThaiBuddhistChronology.INSTANCE; - } - - // JDK8 default methods: - //----------------------------------------------------------------------- - @Override - public ThaiBuddhistDate date(int year, int month, int day) { - return (ThaiBuddhistDate)(getChronology().date(this, year, month, day)); - } - - @Override - public ThaiBuddhistDate dateYearDay(int year, int dayOfYear) { - return (ThaiBuddhistDate)(getChronology().dateYearDay(this, year, dayOfYear)); - } - //----------------------------------------------------------------------- private Object writeReplace() { return new Ser(Ser.THAIBUDDHIST_ERA_TYPE, this); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/DateTimeBuilder.java --- a/src/share/classes/java/time/format/DateTimeBuilder.java Sat Apr 13 20:16:00 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,595 +0,0 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * This file is available under and governed by the GNU General Public - * License version 2 only, as published by the Free Software Foundation. - * However, the following notice accompanied the original version of this - * file: - * - * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of JSR-310 nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package java.time.format; - -import static java.time.temporal.Adjusters.nextOrSame; -import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; -import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; -import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; -import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; -import static java.time.temporal.ChronoField.AMPM_OF_DAY; -import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; -import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; -import static java.time.temporal.ChronoField.DAY_OF_MONTH; -import static java.time.temporal.ChronoField.DAY_OF_WEEK; -import static java.time.temporal.ChronoField.DAY_OF_YEAR; -import static java.time.temporal.ChronoField.EPOCH_DAY; -import static java.time.temporal.ChronoField.EPOCH_MONTH; -import static java.time.temporal.ChronoField.ERA; -import static java.time.temporal.ChronoField.HOUR_OF_AMPM; -import static java.time.temporal.ChronoField.HOUR_OF_DAY; -import static java.time.temporal.ChronoField.MICRO_OF_DAY; -import static java.time.temporal.ChronoField.MICRO_OF_SECOND; -import static java.time.temporal.ChronoField.MILLI_OF_DAY; -import static java.time.temporal.ChronoField.MILLI_OF_SECOND; -import static java.time.temporal.ChronoField.MINUTE_OF_DAY; -import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; -import static java.time.temporal.ChronoField.MONTH_OF_YEAR; -import static java.time.temporal.ChronoField.NANO_OF_DAY; -import static java.time.temporal.ChronoField.NANO_OF_SECOND; -import static java.time.temporal.ChronoField.SECOND_OF_DAY; -import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; -import static java.time.temporal.ChronoField.YEAR; -import static java.time.temporal.ChronoField.YEAR_OF_ERA; - -import java.time.DateTimeException; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.ZoneId; -import java.time.chrono.ChronoLocalDate; -import java.time.chrono.Chronology; -import java.time.chrono.Era; -import java.time.chrono.IsoChronology; -import java.time.chrono.JapaneseChronology; -import java.time.temporal.ChronoField; -import java.time.temporal.ChronoUnit; -import java.time.temporal.Queries; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalField; -import java.time.temporal.TemporalQuery; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Builder that can holds date and time fields and related date and time objects. - *

    - * This class still needs major revision before JDK1.8 ships. - *

    - * The builder is used to hold onto different elements of date and time. - * It holds two kinds of object: - *

      - *
    • a {@code Map} from {@link TemporalField} to {@code long} value, where the - * value may be outside the valid range for the field - *
    • a list of objects, such as {@code Chronology} or {@code ZoneId} - *

    - * - *

    Specification for implementors

    - * This class is mutable and not thread-safe. - * It should only be used from a single thread. - * - * @since 1.8 - */ -final class DateTimeBuilder - implements TemporalAccessor, Cloneable { - - /** - * The map of other fields. - */ - private Map otherFields; - /** - * The map of date-time fields. - */ - private final EnumMap standardFields = new EnumMap(ChronoField.class); - /** - * The chronology. - */ - private Chronology chrono; - /** - * The zone. - */ - private ZoneId zone; - /** - * The date. - */ - private LocalDate date; - /** - * The time. - */ - private LocalTime time; - - //----------------------------------------------------------------------- - /** - * Creates an empty instance of the builder. - */ - public DateTimeBuilder() { - } - - //----------------------------------------------------------------------- - private Long getFieldValue0(TemporalField field) { - if (field instanceof ChronoField) { - return standardFields.get(field); - } else if (otherFields != null) { - return otherFields.get(field); - } - return null; - } - - /** - * Adds a field-value pair to the builder. - *

    - * This adds a field to the builder. - * If the field is not already present, then the field-value pair is added to the map. - * If the field is already present and it has the same value as that specified, no action occurs. - * If the field is already present and it has a different value to that specified, then - * an exception is thrown. - * - * @param field the field to add, not null - * @param value the value to add, not null - * @return {@code this}, for method chaining - * @throws DateTimeException if the field is already present with a different value - */ - DateTimeBuilder addFieldValue(TemporalField field, long value) { - Objects.requireNonNull(field, "field"); - Long old = getFieldValue0(field); // check first for better error message - if (old != null && old.longValue() != value) { - throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this); - } - return putFieldValue0(field, value); - } - - private DateTimeBuilder putFieldValue0(TemporalField field, long value) { - if (field instanceof ChronoField) { - standardFields.put((ChronoField) field, value); - } else { - if (otherFields == null) { - otherFields = new LinkedHashMap(); - } - otherFields.put(field, value); - } - return this; - } - - //----------------------------------------------------------------------- - void addObject(Chronology chrono) { - this.chrono = chrono; - } - - void addObject(ZoneId zone) { - this.zone = zone; - } - - void addObject(LocalDate date) { - this.date = date; - } - - void addObject(LocalTime time) { - this.time = time; - } - - //----------------------------------------------------------------------- - /** - * Resolves the builder, evaluating the date and time. - *

    - * This examines the contents of the builder and resolves it to produce the best - * available date and time, throwing an exception if a problem occurs. - * Calling this method changes the state of the builder. - * - * @return {@code this}, for method chaining - */ - DateTimeBuilder resolve() { - // handle standard fields - mergeDate(); - mergeTime(); - // TODO: cross validate remaining fields? - return this; - } - - private void mergeDate() { - if (standardFields.containsKey(EPOCH_DAY)) { - checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY))); - return; - } - - Era era = null; - if (chrono == IsoChronology.INSTANCE) { - // normalize fields - if (standardFields.containsKey(EPOCH_MONTH)) { - long em = standardFields.remove(EPOCH_MONTH); - addFieldValue(MONTH_OF_YEAR, (em % 12) + 1); - addFieldValue(YEAR, (em / 12) + 1970); - } - } else { - // TODO: revisit EPOCH_MONTH calculation in non-ISO chronology - // Handle EPOCH_MONTH here for non-ISO Chronology - if (standardFields.containsKey(EPOCH_MONTH)) { - long em = standardFields.remove(EPOCH_MONTH); - ChronoLocalDate chronoDate = chrono.date(LocalDate.ofEpochDay(0L)); - chronoDate = chronoDate.plus(em, ChronoUnit.MONTHS); - LocalDate date = LocalDate.ofEpochDay(chronoDate.toEpochDay()); - checkDate(date); - return; - } - List eras = chrono.eras(); - if (!eras.isEmpty()) { - if (standardFields.containsKey(ERA)) { - long index = standardFields.remove(ERA); - era = chrono.eraOf((int) index); - } else { - era = eras.get(eras.size() - 1); // current Era - } - if (standardFields.containsKey(YEAR_OF_ERA)) { - Long y = standardFields.remove(YEAR_OF_ERA); - putFieldValue0(YEAR, y); - } - } - - } - - // build date - if (standardFields.containsKey(YEAR)) { - if (standardFields.containsKey(MONTH_OF_YEAR)) { - if (standardFields.containsKey(DAY_OF_MONTH)) { - int y = Math.toIntExact(standardFields.remove(YEAR)); - int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); - int dom = Math.toIntExact(standardFields.remove(DAY_OF_MONTH)); - LocalDate date; - if (chrono == IsoChronology.INSTANCE) { - date = LocalDate.of(y, moy, dom); - } else { - ChronoLocalDate chronoDate; - if (era == null) { - chronoDate = chrono.date(y, moy, dom); - } else { - chronoDate = era.date(y, moy, dom); - } - date = LocalDate.ofEpochDay(chronoDate.toEpochDay()); - } - checkDate(date); - return; - } - if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) { - if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { - int y = Math.toIntExact(standardFields.remove(YEAR)); - int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); - int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); - int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH)); - LocalDate date; - if (chrono == IsoChronology.INSTANCE) { - date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1)); - } else { - ChronoLocalDate chronoDate; - if (era == null) { - chronoDate = chrono.date(y, moy, 1); - } else { - chronoDate = era.date(y, moy, 1); - } - chronoDate = chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS); - date = LocalDate.ofEpochDay(chronoDate.toEpochDay()); - } - checkDate(date); - return; - } - if (standardFields.containsKey(DAY_OF_WEEK)) { - int y = Math.toIntExact(standardFields.remove(YEAR)); - int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); - int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); - int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK)); - LocalDate date; - if (chrono == IsoChronology.INSTANCE) { - date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow))); - } else { - ChronoLocalDate chronoDate; - if (era == null) { - chronoDate = chrono.date(y, moy, 1); - } else { - chronoDate = era.date(y, moy, 1); - } - chronoDate = chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow))); - date = LocalDate.ofEpochDay(chronoDate.toEpochDay()); - } - checkDate(date); - return; - } - } - } - if (standardFields.containsKey(DAY_OF_YEAR)) { - int y = Math.toIntExact(standardFields.remove(YEAR)); - int doy = Math.toIntExact(standardFields.remove(DAY_OF_YEAR)); - LocalDate date; - if (chrono == IsoChronology.INSTANCE) { - date = LocalDate.ofYearDay(y, doy); - } else { - ChronoLocalDate chronoDate; - if (era == null) { - chronoDate = chrono.dateYearDay(y, doy); - } else { - chronoDate = era.dateYearDay(y, doy); - } - date = LocalDate.ofEpochDay(chronoDate.toEpochDay()); - } - checkDate(date); - return; - } - if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) { - if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { - int y = Math.toIntExact(standardFields.remove(YEAR)); - int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); - int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR)); - LocalDate date; - if (chrono == IsoChronology.INSTANCE) { - date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1)); - } else { - ChronoLocalDate chronoDate; - if (era == null) { - chronoDate = chrono.dateYearDay(y, 1); - } else { - chronoDate = era.dateYearDay(y, 1); - } - chronoDate = chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS); - date = LocalDate.ofEpochDay(chronoDate.toEpochDay()); - } - checkDate(date); - return; - } - if (standardFields.containsKey(DAY_OF_WEEK)) { - int y = Math.toIntExact(standardFields.remove(YEAR)); - int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); - int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK)); - LocalDate date; - if (chrono == IsoChronology.INSTANCE) { - date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow))); - } else { - ChronoLocalDate chronoDate; - if (era == null) { - chronoDate = chrono.dateYearDay(y, 1); - } else { - chronoDate = era.dateYearDay(y, 1); - } - chronoDate = chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow))); - date = LocalDate.ofEpochDay(chronoDate.toEpochDay()); - } - checkDate(date); - return; - } - } - } - } - - private void checkDate(LocalDate date) { - addObject(date); - for (ChronoField field : standardFields.keySet()) { - long val1; - try { - val1 = date.getLong(field); - } catch (DateTimeException ex) { - continue; - } - Long val2 = standardFields.get(field); - if (val1 != val2) { - throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date); - } - } - } - - private void mergeTime() { - if (standardFields.containsKey(CLOCK_HOUR_OF_DAY)) { - long ch = standardFields.remove(CLOCK_HOUR_OF_DAY); - addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch); - } - if (standardFields.containsKey(CLOCK_HOUR_OF_AMPM)) { - long ch = standardFields.remove(CLOCK_HOUR_OF_AMPM); - addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch); - } - if (standardFields.containsKey(AMPM_OF_DAY) && standardFields.containsKey(HOUR_OF_AMPM)) { - long ap = standardFields.remove(AMPM_OF_DAY); - long hap = standardFields.remove(HOUR_OF_AMPM); - addFieldValue(HOUR_OF_DAY, ap * 12 + hap); - } -// if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) { -// long hod = timeFields.remove(HOUR_OF_DAY); -// long moh = timeFields.remove(MINUTE_OF_HOUR); -// addFieldValue(MINUTE_OF_DAY, hod * 60 + moh); -// } -// if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) { -// long mod = timeFields.remove(MINUTE_OF_DAY); -// long som = timeFields.remove(SECOND_OF_MINUTE); -// addFieldValue(SECOND_OF_DAY, mod * 60 + som); -// } - if (standardFields.containsKey(NANO_OF_DAY)) { - long nod = standardFields.remove(NANO_OF_DAY); - addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L); - addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); - } - if (standardFields.containsKey(MICRO_OF_DAY)) { - long cod = standardFields.remove(MICRO_OF_DAY); - addFieldValue(SECOND_OF_DAY, cod / 1000_000L); - addFieldValue(MICRO_OF_SECOND, cod % 1000_000L); - } - if (standardFields.containsKey(MILLI_OF_DAY)) { - long lod = standardFields.remove(MILLI_OF_DAY); - addFieldValue(SECOND_OF_DAY, lod / 1000); - addFieldValue(MILLI_OF_SECOND, lod % 1000); - } - if (standardFields.containsKey(SECOND_OF_DAY)) { - long sod = standardFields.remove(SECOND_OF_DAY); - addFieldValue(HOUR_OF_DAY, sod / 3600); - addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); - addFieldValue(SECOND_OF_MINUTE, sod % 60); - } - if (standardFields.containsKey(MINUTE_OF_DAY)) { - long mod = standardFields.remove(MINUTE_OF_DAY); - addFieldValue(HOUR_OF_DAY, mod / 60); - addFieldValue(MINUTE_OF_HOUR, mod % 60); - } - -// long sod = nod / 1000_000_000L; -// addFieldValue(HOUR_OF_DAY, sod / 3600); -// addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); -// addFieldValue(SECOND_OF_MINUTE, sod % 60); -// addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); - if (standardFields.containsKey(MILLI_OF_SECOND) && standardFields.containsKey(MICRO_OF_SECOND)) { - long los = standardFields.remove(MILLI_OF_SECOND); - long cos = standardFields.get(MICRO_OF_SECOND); - addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000)); - } - - Long hod = standardFields.get(HOUR_OF_DAY); - Long moh = standardFields.get(MINUTE_OF_HOUR); - Long som = standardFields.get(SECOND_OF_MINUTE); - Long nos = standardFields.get(NANO_OF_SECOND); - if (hod != null) { - int hodVal = Math.toIntExact(hod); - if (moh != null) { - int mohVal = Math.toIntExact(moh); - if (som != null) { - int somVal = Math.toIntExact(som); - if (nos != null) { - int nosVal = Math.toIntExact(nos); - addObject(LocalTime.of(hodVal, mohVal, somVal, nosVal)); - } else { - addObject(LocalTime.of(hodVal, mohVal, somVal)); - } - } else { - addObject(LocalTime.of(hodVal, mohVal)); - } - } else { - addObject(LocalTime.of(hodVal, 0)); - } - } - } - - //----------------------------------------------------------------------- - @Override - public boolean isSupported(TemporalField field) { - if (field == null) { - return false; - } - return standardFields.containsKey(field) || - (otherFields != null && otherFields.containsKey(field)) || - (date != null && date.isSupported(field)) || - (time != null && time.isSupported(field)); - } - - @Override - public long getLong(TemporalField field) { - Objects.requireNonNull(field, "field"); - Long value = getFieldValue0(field); - if (value == null) { - if (date != null && date.isSupported(field)) { - return date.getLong(field); - } - if (time != null && time.isSupported(field)) { - return time.getLong(field); - } - throw new DateTimeException("Field not found: " + field); - } - return value; - } - - @SuppressWarnings("unchecked") - @Override - public R query(TemporalQuery query) { - if (query == Queries.zoneId()) { - return (R) zone; - } else if (query == Queries.chronology()) { - return (R) chrono; - } else if (query == Queries.localDate()) { - return (R) date; - } else if (query == Queries.localTime()) { - return (R) time; - } else if (query == Queries.zone() || query == Queries.offset()) { - return query.queryFrom(this); - } else if (query == Queries.precision()) { - return null; // not a complete date/time - } - // inline TemporalAccessor.super.query(query) as an optimization - // non-JDK classes are not permitted to make this optimization - return query.queryFrom(this); - } - - //----------------------------------------------------------------------- - @Override - public String toString() { - StringBuilder buf = new StringBuilder(128); - buf.append("DateTimeBuilder["); - Map fields = new HashMap<>(); - fields.putAll(standardFields); - if (otherFields != null) { - fields.putAll(otherFields); - } - if (fields.size() > 0) { - buf.append("fields=").append(fields); - } - buf.append(", ").append(chrono); - buf.append(", ").append(zone); - buf.append(", ").append(date); - buf.append(", ").append(time); - buf.append(']'); - return buf.toString(); - } - -} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/DateTimeFormatStyleProvider.java --- a/src/share/classes/java/time/format/DateTimeFormatStyleProvider.java Sat Apr 13 20:16:00 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * This file is available under and governed by the GNU General Public - * License version 2 only, as published by the Free Software Foundation. - * However, the following notice accompanied the original version of this - * file: - * - * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of JSR-310 nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package java.time.format; - -import java.text.SimpleDateFormat; -import java.time.chrono.Chronology; -import java.util.Locale; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import sun.util.locale.provider.LocaleProviderAdapter; -import sun.util.locale.provider.LocaleResources; - -/** - * A provider to obtain date-time formatters for a style. - *

    - * - *

    Specification for implementors

    - * This implementation is based on extraction of data from a {@link SimpleDateFormat}. - * This class is immutable and thread-safe. - * This Implementations caches the returned formatters. - * - * @since 1.8 - */ -final class DateTimeFormatStyleProvider { - // TODO: Better implementation based on CLDR - - /** Cache of formatters. */ - private static final ConcurrentMap FORMATTER_CACHE = new ConcurrentHashMap<>(16, 0.75f, 2); - - private DateTimeFormatStyleProvider() {} - - /** - * Gets an Instance of the provider of format styles. - * - * @return the provider, not null - */ - static DateTimeFormatStyleProvider getInstance() { - return new DateTimeFormatStyleProvider(); - } - - /** - * Gets a localized date, time or date-time formatter. - *

    - * The formatter will be the most appropriate to use for the date and time style in the locale. - * For example, some locales will use the month name while others will use the number. - * - * @param dateStyle the date formatter style to obtain, null to obtain a time formatter - * @param timeStyle the time formatter style to obtain, null to obtain a date formatter - * @param chrono the chronology to use, not null - * @param locale the locale to use, not null - * @return the date-time formatter, not null - * @throws IllegalArgumentException if both format styles are null or if the locale is not recognized - */ - public DateTimeFormatter getFormatter( - FormatStyle dateStyle, FormatStyle timeStyle, Chronology chrono, Locale locale) { - if (dateStyle == null && timeStyle == null) { - throw new IllegalArgumentException("Date and Time style must not both be null"); - } - String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle; - Object cached = FORMATTER_CACHE.get(key); - if (cached != null) { - return (DateTimeFormatter) cached; - } - - LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() - .getLocaleResources(locale); - String pattern = lr.getCldrDateTimePattern(convertStyle(timeStyle), convertStyle(dateStyle), - chrono.getCalendarType()); - DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale); - FORMATTER_CACHE.putIfAbsent(key, formatter); - return formatter; - } - - /** - * Converts the enum style to the java.util.Calendar style. Standalone styles - * are not supported. - * - * @param style the enum style - * @return the int style, or -1 if style is null, indicating unrequired - */ - private int convertStyle(FormatStyle style) { - if (style == null) { - return -1; - } - return style.ordinal(); // indices happen to align - } - -} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/DateTimeFormatter.java --- a/src/share/classes/java/time/format/DateTimeFormatter.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/format/DateTimeFormatter.java Sat Apr 13 21:51:36 2013 +0100 @@ -79,45 +79,343 @@ import java.time.DateTimeException; import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.chrono.Chronology; +import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser; -import java.time.chrono.Chronology; import java.time.temporal.ChronoField; import java.time.temporal.IsoFields; import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Formatter for printing and parsing date-time objects. *

    * This class provides the main application entry point for printing and parsing * and provides common implementations of {@code DateTimeFormatter}: - *

      - *
    • Using pattern letters, such as {@code yyyy-MMM-dd} - *
    • Using localized styles, such as {@code long} or {@code medium} - *
    • Using predefined constants, such as {@code ISO_LOCAL_DATE} - *

    + *
      + *
    • Using predefined constants, such as {@link #ISO_LOCAL_DATE}
    • + *
    • Using pattern letters, such as {@code uuuu-MMM-dd}
    • + *
    • Using localized styles, such as {@code long} or {@code medium}
    • + *
    + *

    + * More complex formatters are provided by + * {@link DateTimeFormatterBuilder DateTimeFormatterBuilder}. * *

    - * In most cases, provided formatters will be sufficient. - * For more complex formatters, a {@link DateTimeFormatterBuilder builder} is provided. - * The main date-time classes provide two methods - one for printing, - * {@code toString(DateTimeFormatter formatter)}, and one for parsing, + * The main date-time classes provide two methods - one for formatting, + * {@code format(DateTimeFormatter formatter)}, and one for parsing, * {@code parse(CharSequence text, DateTimeFormatter formatter)}. - * For example: - *

    + * 

    For example: + *

      *  String text = date.toString(formatter);
      *  LocalDate date = LocalDate.parse(text, formatter);
    + * 
    + *

    + * In addition to the format, formatters can be created with desired Locale, + * Chronology, ZoneId, and formatting symbols. + *

    + * The {@link #withLocale withLocale} method returns a new formatter that + * overrides the locale. The locale affects some aspects of formatting and + * parsing. For example, the {@link #ofLocalizedDate ofLocalizedDate} provides a + * formatter that uses the locale specific date format. + *

    + * The {@link #withChronology withChronology} method returns a new formatter + * that overrides the chronology. If overridden, the date-time value is + * converted to the chronology before formatting. During parsing the date-time + * value is converted to the chronology before it is returned. + *

    + * The {@link #withZone withZone} method returns a new formatter that overrides + * the zone. If overridden, the date-time value is converted to a ZonedDateTime + * with the requested ZoneId before formatting. During parsing the ZoneId is + * applied before the value is returned. + *

    + * The {@link #withSymbols withSymbols} method returns a new formatter that + * overrides the {@link DateTimeFormatSymbols}. The symbols are used for + * formatting and parsing. + *

    + * Some applications may need to use the older {@link Format java.text.Format} + * class for formatting. The {@link #toFormat()} method returns an + * implementation of {@code java.text.Format}. + *

    + *

    Predefined Formatters

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    FormatterDescriptionExample
    {@link #ofLocalizedDate ofLocalizedDate(dateStyle)} Formatter with date style from the locale '2011-12-03'
    {@link #ofLocalizedTime ofLocalizedTime(timeStyle)} Formatter with time style from the locale '10:15:30'
    {@link #ofLocalizedDateTime ofLocalizedDateTime(dateTimeStyle)} Formatter with a style for date and time from the locale '3 Jun 2008 11:05:30'
    {@link #ofLocalizedDateTime ofLocalizedDateTime(dateStyle,timeStyle)} + * Formatter with date and time styles from the locale '3 Jun 2008 11:05'
    {@link #BASIC_ISO_DATE}Basic ISO date '20111203'
    {@link #ISO_LOCAL_DATE} ISO Local Date '2011-12-03'
    {@link #ISO_OFFSET_DATE} ISO Date with offset '2011-12-03+01:00'
    {@link #ISO_DATE} ISO Date with or without offset '2011-12-03+01:00'; '2011-12-03'
    {@link #ISO_LOCAL_TIME} Time without offset '10:15:30'
    {@link #ISO_OFFSET_TIME} Time with offset '10:15:30+01:00'
    {@link #ISO_TIME} Time with or without offset '10:15:30+01:00'; '10:15:30'
    {@link #ISO_LOCAL_DATE_TIME} ISO Local Date and Time '2011-12-03T10:15:30'
    {@link #ISO_OFFSET_DATE_TIME} Date Time with Offset + * 2011-12-03T10:15:30+01:00'
    {@link #ISO_ZONED_DATE_TIME} Zoned Date Time '2011-12-03T10:15:30+01:00[Europe/Paris]'
    {@link #ISO_DATE_TIME} Date and time with ZoneId '2011-12-03T10:15:30+01:00[Europe/Paris]'
    {@link #ISO_ORDINAL_DATE} Year and day of year '2012-337'
    {@link #ISO_WEEK_DATE} Year and Week 2012-W48-6'
    {@link #ISO_INSTANT} Date and Time of an Instant '2011-12-03T10:15:30Z'
    {@link #RFC_1123_DATE_TIME} RFC 1123 / RFC 822 'Tue, 3 Jun 2008 11:05:30 GMT'
    + * + *

    Patterns for Formatting and Parsing

    + * Patterns are based on a simple sequence of letters and symbols. + * A pattern is used to create a Formatter using the + * {@link #ofPattern(String)} and {@link #ofPattern(String, Locale)} methods. + * For example, + * {@code "d MMM uuuu"} will format 2011-12-03 as '3 Dec 2011'. + * A formatter created from a pattern can be used as many times as necessary, + * it is immutable and is thread-safe. + *

    + * For example: + *

    + *  DateTimeFormatter formatter = DateTimeFormatter.pattern("yyyy MM dd");
    + *  String text = date.toString(formatter);
    + *  LocalDate date = LocalDate.parse(text, formatter);
    + * 
    + *

    + * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. The + * following pattern letters are defined: + *

    + *  Symbol  Meaning                     Presentation      Examples
    + *  ------  -------                     ------------      -------
    + *   G       era                         text              AD; Anno Domini; A
    + *   u       year                        year              2004; 04
    + *   y       year-of-era                 year              2004; 04
    + *   D       day-of-year                 number            189
    + *   M/L     month-of-year               number/text       7; 07; Jul; July; J
    + *   d       day-of-month                number            10
    + *
    + *   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
    + *   Y       week-based-year             year              1996; 96
    + *   w       week-of-week-based-year     number            27
    + *   W       week-of-month               number            4
    + *   E       day-of-week                 text              Tue; Tuesday; T
    + *   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
    + *   F       week-of-month               number            3
    + *
    + *   a       am-pm-of-day                text              PM
    + *   h       clock-hour-of-am-pm (1-12)  number            12
    + *   K       hour-of-am-pm (0-11)        number            0
    + *   k       clock-hour-of-am-pm (1-24)  number            0
    + *
    + *   H       hour-of-day (0-23)          number            0
    + *   m       minute-of-hour              number            30
    + *   s       second-of-minute            number            55
    + *   S       fraction-of-second          fraction          978
    + *   A       milli-of-day                number            1234
    + *   n       nano-of-second              number            987654321
    + *   N       nano-of-day                 number            1234000000
    + *
    + *   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
    + *   z       time-zone name              zone-name         Pacific Standard Time; PST
    + *   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
    + *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
    + *   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
    + *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
    + *
    + *   p       pad next                    pad modifier      1
    + *
    + *   '       escape for text             delimiter
    + *   ''      single quote                literal           '
    + *   [       optional section start
    + *   ]       optional section end
    + *   #       reserved for future use
    + *   {       reserved for future use
    + *   }       reserved for future use
      * 
    - * Some aspects of formatting and parsing are dependent on the locale. - * The locale can be changed using the {@link #withLocale(Locale)} method - * which returns a new formatter in the requested locale. + *

    + * The count of pattern letters determines the format. + *

    + * Text: The text style is determined based on the number of pattern + * letters used. Less than 4 pattern letters will use the + * {@link TextStyle#SHORT short form}. Exactly 4 pattern letters will use the + * {@link TextStyle#FULL full form}. Exactly 5 pattern letters will use the + * {@link TextStyle#NARROW narrow form}. + * Pattern letters 'L', 'c', and 'q' specify the stand-alone form of the text styles. + *

    + * Number: If the count of letters is one, then the value is output using + * the minimum number of digits and without padding. Otherwise, the count of digits + * is used as the width of the output field, with the value zero-padded as necessary. + * The following pattern letters have constraints on the count of letters. + * Only one letter of 'c' and 'F' can be specified. + * Up to two letters of 'd', 'H', 'h', 'K', 'k', 'm', and 's' can be specified. + * Up to three letters of 'D' can be specified. + *

    + * Number/Text: If the count of pattern letters is 3 or greater, use the + * Text rules above. Otherwise use the Number rules above. + *

    + * Fraction: Outputs the nano-of-second field as a fraction-of-second. + * The nano-of-second value has nine digits, thus the count of pattern letters + * is from 1 to 9. If it is less than 9, then the nano-of-second value is + * truncated, with only the most significant digits being output. When parsing + * in strict mode, the number of parsed digits must match the count of pattern + * letters. When parsing in lenient mode, the number of parsed digits must be at + * least the count of pattern letters, up to 9 digits. + *

    + * Year: The count of letters determines the minimum field width below + * which padding is used. If the count of letters is two, then a + * {@link DateTimeFormatterBuilder#appendValueReduced reduced} two digit form is + * used. For printing, this outputs the rightmost two digits. For parsing, this + * will parse using the base value of 2000, resulting in a year within the range + * 2000 to 2099 inclusive. If the count of letters is less than four (but not + * two), then the sign is only output for negative years as per + * {@link SignStyle#NORMAL}. Otherwise, the sign is output if the pad width is + * exceeded, as per {@link SignStyle#EXCEEDS_PAD}. + *

    + * ZoneId: This outputs the time-zone ID, such as 'Europe/Paris'. If the + * count of letters is two, then the time-zone ID is output. Any other count of + * letters throws {@code IllegalArgumentException}. + *

    + * Zone names: This outputs the display name of the time-zone ID. If the + * count of letters is one, two or three, then the short name is output. If the + * count of letters is four, then the full name is output. Five or more letters + * throws {@code IllegalArgumentException}. *

    - * Some applications may need to use the older {@link Format} class for formatting. - * The {@link #toFormat()} method returns an implementation of the old API. + * Offset X and x: This formats the offset based on the number of pattern + * letters. One letter outputs just the hour, such as '+01', unless the minute + * is non-zero in which case the minute is also output, such as '+0130'. Two + * letters outputs the hour and minute, without a colon, such as '+0130'. Three + * letters outputs the hour and minute, with a colon, such as '+01:30'. Four + * letters outputs the hour and minute and optional second, without a colon, + * such as '+013015'. Five letters outputs the hour and minute and optional + * second, with a colon, such as '+01:30:15'. Six or more letters throws + * {@code IllegalArgumentException}. Pattern letter 'X' (upper case) will output + * 'Z' when the offset to be output would be zero, whereas pattern letter 'x' + * (lower case) will output '+00', '+0000', or '+00:00'. + *

    + * Offset O: This formats the localized offset based on the number of + * pattern letters. One letter outputs the {@linkplain TextStyle#SHORT short} + * form of the localized offset, which is localized offset text, such as 'GMT', + * with hour without leading zero, optional 2-digit minute and second if + * non-zero, and colon, for example 'GMT+8'. Four letters outputs the + * {@linkplain TextStyle#FULL full} form, which is localized offset text, + * such as 'GMT, with 2-digit hour and minute field, optional second field + * if non-zero, and colon, for example 'GMT+08:00'. Any other count of letters + * throws {@code IllegalArgumentException}. + *

    + * Offset Z: This formats the offset based on the number of pattern + * letters. One, two or three letters outputs the hour and minute, without a + * colon, such as '+0130'. The output will be '+0000' when the offset is zero. + * Four letters outputs the {@linkplain TextStyle#FULL full} form of localized + * offset, equivalent to four letters of Offset-O. The output will be the + * corresponding localized offset text if the offset is zero. Five + * letters outputs the hour, minute, with optional second if non-zero, with + * colon. It outputs 'Z' if the offset is zero. + * Six or more letters throws {@code IllegalArgumentException}. + *

    + * Optional section: The optional section markers work exactly like + * calling {@link DateTimeFormatterBuilder#optionalStart()} and + * {@link DateTimeFormatterBuilder#optionalEnd()}. + *

    + * Pad modifier: Modifies the pattern that immediately follows to be + * padded with spaces. The pad width is determined by the number of pattern + * letters. This is the same as calling + * {@link DateTimeFormatterBuilder#padNext(int)}. + *

    + * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to + * a width of 2. + *

    + * Any unrecognized letter is an error. Any non-letter character, other than + * '[', ']', '{', '}', '#' and the single quote will be output directly. + * Despite this, it is recommended to use single quotes around all characters + * that you want to output directly to ensure that future changes do not break + * your application. * *

    Specification for implementors

    * This class is immutable and thread-safe. @@ -139,6 +437,14 @@ */ private final DateTimeFormatSymbols symbols; /** + * The resolver style to use, not null. + */ + private final ResolverStyle resolverStyle; + /** + * The fields to use in resolving, null for all fields. + */ + private final Set resolverFields; + /** * The chronology to use for formatting, null for no override. */ private final Chronology chrono; @@ -151,129 +457,17 @@ /** * Creates a formatter using the specified pattern. *

    - * This method will create a formatter based on a simple pattern of letters and symbols. - * For example, {@code d MMM yyyy} will format 2011-12-03 as '3 Dec 2011'. - *

    - * The returned formatter will use the default locale, but this can be changed - * using {@link DateTimeFormatter#withLocale(Locale)}. - *

    - * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. - * The following pattern letters are defined: - *

    -     *  Symbol  Meaning                     Presentation      Examples
    -     *  ------  -------                     ------------      -------
    -     *   G       era                         text              A; AD; Anno Domini
    -     *   y       year                        year              2004; 04
    -     *   D       day-of-year                 number            189
    -     *   M       month-of-year               number/text       7; 07; Jul; July; J
    -     *   d       day-of-month                number            10
    -     *
    -     *   Q       quarter-of-year             number/text       3; 03; Q3
    -     *   Y       week-based-year             year              1996; 96
    -     *   w       week-of-year                number            27
    -     *   W       week-of-month               number            27
    -     *   e       localized day-of-week       number            2; Tue; Tuesday; T
    -     *   E       day-of-week                 number/text       2; Tue; Tuesday; T
    -     *   F       week-of-month               number            3
    -     *
    -     *   a       am-pm-of-day                text              PM
    -     *   h       clock-hour-of-am-pm (1-12)  number            12
    -     *   K       hour-of-am-pm (0-11)        number            0
    -     *   k       clock-hour-of-am-pm (1-24)  number            0
    -     *
    -     *   H       hour-of-day (0-23)          number            0
    -     *   m       minute-of-hour              number            30
    -     *   s       second-of-minute            number            55
    -     *   S       fraction-of-second          fraction          978
    -     *   A       milli-of-day                number            1234
    -     *   n       nano-of-second              number            987654321
    -     *   N       nano-of-day                 number            1234000000
    -     *
    -     *   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
    -     *   z       time-zone name              zone-name         Pacific Standard Time; PST
    -     *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
    -     *   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
    -     *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
    -     *
    -     *   p       pad next                    pad modifier      1
    -     *
    -     *   '       escape for text             delimiter
    -     *   ''      single quote                literal           '
    -     *   [       optional section start
    -     *   ]       optional section end
    -     *   {}      reserved for future use
    -     * 
    - *

    - * The count of pattern letters determine the format. - *

    - * Text: The text style is determined based on the number of pattern letters used. - * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}. - * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}. - * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}. + * This method will create a formatter based on a simple + * pattern of letters and symbols + * as described in the class documentation. + * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'. *

    - * Number: If the count of letters is one, then the value is output using the minimum number - * of digits and without padding as per {@link DateTimeFormatterBuilder#appendValue(java.time.temporal.TemporalField)}. - * Otherwise, the count of digits is used as the width of the output field as per - * {@link DateTimeFormatterBuilder#appendValue(java.time.temporal.TemporalField, int)}. - *

    - * Number/Text: If the count of pattern letters is 3 or greater, use the Text rules above. - * Otherwise use the Number rules above. - *

    - * Fraction: Outputs the nano-of-second field as a fraction-of-second. - * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9. - * If it is less than 9, then the nano-of-second value is truncated, with only the most - * significant digits being output. - * When parsing in strict mode, the number of parsed digits must match the count of pattern letters. - * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern - * letters, up to 9 digits. - *

    - * Year: The count of letters determines the minimum field width below which padding is used. - * If the count of letters is two, then a {@link DateTimeFormatterBuilder#appendValueReduced reduced} - * two digit form is used. - * For printing, this outputs the rightmost two digits. For parsing, this will parse using the - * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. - * If the count of letters is less than four (but not two), then the sign is only output for negative - * years as per {@link SignStyle#NORMAL}. - * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD} - *

    - * ZoneId: This outputs the time-zone ID, such as 'Europe/Paris'. - * If the count of letters is two, then the time-zone ID is output. - * Any other count of letters throws {@code IllegalArgumentException}. + * The formatter will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. + * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter + * Alternatively use the {@link #ofPattern(String, Locale)} variant of this method. *

    - * Zone names: This outputs the display name of the time-zone ID. - * If the count of letters is one, two or three, then the short name is output. - * If the count of letters is four, then the full name is output. - * Five or more letters throws {@code IllegalArgumentException}. - *

    - * Offset X and x: This formats the offset based on the number of pattern letters. - * One letter outputs just the hour', such as '+01', unless the minute is non-zero - * in which case the minute is also output, such as '+0130'. - * Two letters outputs the hour and minute, without a colon, such as '+0130'. - * Three letters outputs the hour and minute, with a colon, such as '+01:30'. - * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'. - * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'. - * Six or more letters throws {@code IllegalArgumentException}. - * Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero, - * whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'. - *

    - * Offset Z: This formats the offset based on the number of pattern letters. - * One, two or three letters outputs the hour and minute, without a colon, such as '+0130'. - * Four or more letters throws {@code IllegalArgumentException}. - * The output will be '+0000' when the offset is zero. - *

    - * Optional section: The optional section markers work exactly like calling - * {@link DateTimeFormatterBuilder#optionalStart()} and {@link DateTimeFormatterBuilder#optionalEnd()}. - *

    - * Pad modifier: Modifies the pattern that immediately follows to be padded with spaces. - * The pad width is determined by the number of pattern letters. - * This is the same as calling {@link DateTimeFormatterBuilder#padNext(int)}. - *

    - * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2. - *

    - * Any unrecognized letter is an error. - * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly. - * Despite this, it is recommended to use single quotes around all characters that you want to - * output directly to ensure that future changes do not break your application. + * The returned formatter has no override chronology or zone. + * It uses {@link ResolverStyle#SMART SMART} resolver style. * * @param pattern the pattern to use, not null * @return the formatter based on the pattern, not null @@ -285,15 +479,18 @@ } /** - * Creates a formatter using the specified pattern. - *

    - * This method will create a formatter based on a simple pattern of letters and symbols. - * For example, {@code d MMM yyyy} will format 2011-12-03 as '3 Dec 2011'. + * Creates a formatter using the specified pattern and locale. *

    - * See {@link #ofPattern(String)} for details of the pattern. + * This method will create a formatter based on a simple + * pattern of letters and symbols + * as described in the class documentation. + * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'. *

    - * The returned formatter will use the specified locale, but this can be changed - * using {@link DateTimeFormatter#withLocale(Locale)}. + * The formatter will use the specified locale. + * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter + *

    + * The returned formatter has no override chronology or zone. + * It uses {@link ResolverStyle#SMART SMART} resolver style. * * @param pattern the pattern to use, not null * @param locale the locale to use, not null @@ -307,7 +504,7 @@ //----------------------------------------------------------------------- /** - * Returns a locale specific date format. + * Returns a locale specific date format for the ISO chronology. *

    * This returns a formatter that will format or parse a date. * The exact format pattern used varies by locale. @@ -320,17 +517,22 @@ * Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param dateStyle the formatter style to obtain, not null * @return the date formatter, not null */ public static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) { Objects.requireNonNull(dateStyle, "dateStyle"); - return new DateTimeFormatterBuilder().appendLocalized(dateStyle, null).toFormatter(); + return new DateTimeFormatterBuilder().appendLocalized(dateStyle, null) + .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } /** - * Returns a locale specific time format. + * Returns a locale specific time format for the ISO chronology. *

    * This returns a formatter that will format or parse a time. * The exact format pattern used varies by locale. @@ -343,17 +545,22 @@ * Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param timeStyle the formatter style to obtain, not null * @return the time formatter, not null */ public static DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle) { Objects.requireNonNull(timeStyle, "timeStyle"); - return new DateTimeFormatterBuilder().appendLocalized(null, timeStyle).toFormatter(); + return new DateTimeFormatterBuilder().appendLocalized(null, timeStyle) + .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } /** - * Returns a locale specific date-time formatter, which is typically of short length. + * Returns a locale specific date-time formatter for the ISO chronology. *

    * This returns a formatter that will format or parse a date-time. * The exact format pattern used varies by locale. @@ -366,17 +573,22 @@ * Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param dateTimeStyle the formatter style to obtain, not null * @return the date-time formatter, not null */ public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateTimeStyle) { Objects.requireNonNull(dateTimeStyle, "dateTimeStyle"); - return new DateTimeFormatterBuilder().appendLocalized(dateTimeStyle, dateTimeStyle).toFormatter(); + return new DateTimeFormatterBuilder().appendLocalized(dateTimeStyle, dateTimeStyle) + .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } /** - * Returns a locale specific date and time format. + * Returns a locale specific date and time format for the ISO chronology. *

    * This returns a formatter that will format or parse a date-time. * The exact format pattern used varies by locale. @@ -389,6 +601,10 @@ * Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param dateStyle the date formatter style to obtain, not null * @param timeStyle the time formatter style to obtain, not null @@ -397,13 +613,14 @@ public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle) { Objects.requireNonNull(dateStyle, "dateStyle"); Objects.requireNonNull(timeStyle, "timeStyle"); - return new DateTimeFormatterBuilder().appendLocalized(dateStyle, timeStyle).toFormatter(); + return new DateTimeFormatterBuilder().appendLocalized(dateStyle, timeStyle) + .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date without an offset, - * such as '2011-12-03'. + * The ISO date formatter that formats or parses a date without an + * offset, such as '2011-12-03'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended local date format. @@ -418,23 +635,27 @@ *

  • A dash *
  • Two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}. * This is pre-padded by zero to ensure two digits. - *

    + * + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_LOCAL_DATE; static { ISO_LOCAL_DATE = new DateTimeFormatterBuilder() - .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) - .appendLiteral('-') - .appendValue(MONTH_OF_YEAR, 2) - .appendLiteral('-') - .appendValue(DAY_OF_MONTH, 2) - .toFormatter(); + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral('-') + .appendValue(MONTH_OF_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_MONTH, 2) + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date with an offset, - * such as '2011-12-03+01:00'. + * The ISO date formatter that formats or parses a date with an + * offset, such as '2011-12-03+01:00'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset date format. @@ -444,20 +665,24 @@ *

  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_OFFSET_DATE; static { ISO_OFFSET_DATE = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE) - .appendOffsetId() - .toFormatter(); + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .appendOffsetId() + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date with the + * The ISO date formatter that formats or parses a date with the * offset if available, such as '2011-12-03' or '2011-12-03+01:00'. *

    * This returns an immutable formatter capable of formatting and parsing @@ -469,25 +694,29 @@ *

  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * *

    * As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_DATE; static { ISO_DATE = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE) - .optionalStart() - .appendOffsetId() - .toFormatter(); + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .optionalStart() + .appendOffsetId() + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO time formatter that formats or parses a time without an offset, - * such as '10:15' or '10:15:30'. + * The ISO time formatter that formats or parses a time without an + * offset, such as '10:15' or '10:15:30'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended local time format. @@ -506,26 +735,29 @@ *

  • A decimal point *
  • One to nine digits for the {@link ChronoField#NANO_OF_SECOND nano-of-second}. * As many digits will be output as required. - *

    + * + *

    + * The returned formatter has no override chronology or zone. + * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_LOCAL_TIME; static { ISO_LOCAL_TIME = new DateTimeFormatterBuilder() - .appendValue(HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(MINUTE_OF_HOUR, 2) - .optionalStart() - .appendLiteral(':') - .appendValue(SECOND_OF_MINUTE, 2) - .optionalStart() - .appendFraction(NANO_OF_SECOND, 0, 9, true) - .toFormatter(); + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(NANO_OF_SECOND, 0, 9, true) + .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** - * Returns the ISO time formatter that formats or parses a time with an offset, - * such as '10:15+01:00' or '10:15:30+01:00'. + * The ISO time formatter that formats or parses a time with an + * offset, such as '10:15+01:00' or '10:15:30+01:00'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset time format. @@ -535,20 +767,23 @@ *

  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * + *

    + * The returned formatter has no override chronology or zone. + * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_OFFSET_TIME; static { ISO_OFFSET_TIME = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_TIME) - .appendOffsetId() - .toFormatter(); + .parseCaseInsensitive() + .append(ISO_LOCAL_TIME) + .appendOffsetId() + .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** - * Returns the ISO time formatter that formats or parses a time, with the + * The ISO time formatter that formats or parses a time, with the * offset if available, such as '10:15', '10:15:30' or '10:15:30+01:00'. *

    * This returns an immutable formatter capable of formatting and parsing @@ -560,25 +795,28 @@ *

  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * *

    * As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. + *

    + * The returned formatter has no override chronology or zone. + * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_TIME; static { ISO_TIME = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_TIME) - .optionalStart() - .appendOffsetId() - .toFormatter(); + .parseCaseInsensitive() + .append(ISO_LOCAL_TIME) + .optionalStart() + .appendOffsetId() + .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date-time - * without an offset, such as '2011-12-03T10:15:30'. + * The ISO date-time formatter that formats or parses a date-time without + * an offset, such as '2011-12-03T10:15:30'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset date-time format. @@ -587,22 +825,26 @@ *

  • The {@link #ISO_LOCAL_DATE} *
  • The letter 'T'. Parsing is case insensitive. *
  • The {@link #ISO_LOCAL_TIME} - *

    + * + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_LOCAL_DATE_TIME; static { ISO_LOCAL_DATE_TIME = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE) - .appendLiteral('T') - .append(ISO_LOCAL_TIME) - .toFormatter(); + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .appendLiteral('T') + .append(ISO_LOCAL_TIME) + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date-time - * with an offset, such as '2011-12-03T10:15:30+01:00'. + * The ISO date-time formatter that formats or parses a date-time with an + * offset, such as '2011-12-03T10:15:30+01:00'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset date-time format. @@ -612,25 +854,30 @@ *

  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_OFFSET_DATE_TIME; static { ISO_OFFSET_DATE_TIME = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_TIME) - .appendOffsetId() - .toFormatter(); + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_TIME) + .appendOffsetId() + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date-time with + * The ISO-like date-time formatter that formats or parses a date-time with * offset and zone, such as '2011-12-03T10:15:30+01:00[Europe/Paris]'. *

    * This returns an immutable formatter capable of formatting and parsing * a format that extends the ISO-8601 extended offset date-time format * to add the time-zone. + * The section in square brackets is not part of the ISO-8601 standard. * The format consists of: *

      *
    • The {@link #ISO_OFFSET_DATE_TIME} @@ -639,28 +886,33 @@ *
    • The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard. * Parsing is case sensitive. *
    • A close square bracket ']'. - *

    + * + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_ZONED_DATE_TIME; static { ISO_ZONED_DATE_TIME = new DateTimeFormatterBuilder() - .append(ISO_OFFSET_DATE_TIME) - .optionalStart() - .appendLiteral('[') - .parseCaseSensitive() - .appendZoneRegionId() - .appendLiteral(']') - .toFormatter(); + .append(ISO_OFFSET_DATE_TIME) + .optionalStart() + .appendLiteral('[') + .parseCaseSensitive() + .appendZoneRegionId() + .appendLiteral(']') + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date-time - * with the offset and zone if available, such as '2011-12-03T10:15:30', + * The ISO-like date-time formatter that formats or parses a date-time with + * the offset and zone if available, such as '2011-12-03T10:15:30', * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'. *

    * This returns an immutable formatter capable of formatting and parsing - * the ISO-8601 extended offset date-time format. + * the ISO-8601 extended local or offset date-time format, as well as the + * extended non-ISO form specifying the time-zone. * The format consists of: *

      *
    • The {@link #ISO_LOCAL_DATE_TIME} @@ -672,28 +924,32 @@ *
    • The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard. * Parsing is case sensitive. *
    • A close square bracket ']'. - *

    + * *

    * As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_DATE_TIME; static { ISO_DATE_TIME = new DateTimeFormatterBuilder() - .append(ISO_LOCAL_DATE_TIME) - .optionalStart() - .appendOffsetId() - .optionalStart() - .appendLiteral('[') - .parseCaseSensitive() - .appendZoneRegionId() - .appendLiteral(']') - .toFormatter(); + .append(ISO_LOCAL_DATE_TIME) + .optionalStart() + .appendOffsetId() + .optionalStart() + .appendLiteral('[') + .parseCaseSensitive() + .appendZoneRegionId() + .appendLiteral(']') + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses the ordinal date + * The ISO date formatter that formats or parses the ordinal date * without an offset, such as '2012-337'. *

    * This returns an immutable formatter capable of formatting and parsing @@ -710,26 +966,30 @@ *

  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * *

    * As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_ORDINAL_DATE; static { ISO_ORDINAL_DATE = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) - .appendLiteral('-') - .appendValue(DAY_OF_YEAR, 3) - .optionalStart() - .appendOffsetId() - .toFormatter(); + .parseCaseInsensitive() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral('-') + .appendValue(DAY_OF_YEAR, 3) + .optionalStart() + .appendOffsetId() + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses the week-based date + * The ISO date formatter that formats or parses the week-based date * without an offset, such as '2012-W48-6'. *

    * This returns an immutable formatter capable of formatting and parsing @@ -750,50 +1010,67 @@ *

  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * *

    * As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_WEEK_DATE; static { ISO_WEEK_DATE = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .appendValue(IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD) - .appendLiteral("-W") - .appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2) - .appendLiteral('-') - .appendValue(DAY_OF_WEEK, 1) - .optionalStart() - .appendOffsetId() - .toFormatter(); + .parseCaseInsensitive() + .appendValue(IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral("-W") + .appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_WEEK, 1) + .optionalStart() + .appendOffsetId() + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** - * Returns the ISO instant formatter that formats or parses an instant in UTC. + * The ISO instant formatter that formats or parses an instant in UTC, + * such as '2011-12-03T10:15:30Z'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 instant format. + *

    + * This is a special case formatter intended to allow a human readable form + * of an {@link java.time.Instant}. The {@code Instant} class is designed to + * only represent a point in time and internally stores a value in nanoseconds + * from a fixed epoch of 1970-01-01Z. As such, an {@code Instant} cannot be + * formatted as a date or time without providing some form of time-zone. + * This formatter allows the {@code Instant} to be formatted, by providing + * a suitable conversion using {@code ZoneOffset.UTC}. + *

    * The format consists of: *

      *
    • The {@link #ISO_OFFSET_DATE_TIME} where the instant is converted from * {@link ChronoField#INSTANT_SECONDS} and {@link ChronoField#NANO_OF_SECOND} * using the {@code UTC} offset. Parsing is case insensitive. - *

    + * + *

    + * The returned formatter has no override chronology or zone. + * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_INSTANT; static { ISO_INSTANT = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .appendInstant() - .toFormatter(); + .parseCaseInsensitive() + .appendInstant() + .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** - * Returns the ISO date formatter that formats or parses a date without an offset, - * such as '20111203'. + * The ISO date formatter that formats or parses a date without an + * offset, such as '20111203'. *

    * This returns an immutable formatter capable of formatting and parsing * the ISO-8601 basic local date format. @@ -809,21 +1086,25 @@ *

  • The {@link ZoneOffset#getId() offset ID} without colons. If the offset has * seconds then they will be handled even though this is not part of the ISO-8601 standard. * Parsing is case insensitive. - *

    + * *

    * As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter BASIC_ISO_DATE; static { BASIC_ISO_DATE = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .appendValue(YEAR, 4) - .appendValue(MONTH_OF_YEAR, 2) - .appendValue(DAY_OF_MONTH, 2) - .optionalStart() - .appendOffset("+HHMMss", "Z") - .toFormatter(); + .parseCaseInsensitive() + .appendValue(YEAR, 4) + .appendValue(MONTH_OF_YEAR, 2) + .appendValue(DAY_OF_MONTH, 2) + .optionalStart() + .appendOffset("+HHMMss", "Z") + .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- @@ -862,9 +1143,13 @@ *

  • A space *
  • The {@link ZoneOffset#getId() offset ID} without colons or seconds. * An offset of zero uses "GMT". North American zone names and military zone names are not handled. - *

    + * *

    * Parsing is case insensitive. + *

    + * The returned formatter has a chronology of ISO set to ensure dates in + * other calendar systems are correctly converted. + * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. */ public static final DateTimeFormatter RFC_1123_DATE_TIME; static { @@ -892,28 +1177,28 @@ moy.put(11L, "Nov"); moy.put(12L, "Dec"); RFC_1123_DATE_TIME = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .parseLenient() - .optionalStart() - .appendText(DAY_OF_WEEK, dow) - .appendLiteral(", ") - .optionalEnd() - .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) - .appendLiteral(' ') - .appendText(MONTH_OF_YEAR, moy) - .appendLiteral(' ') - .appendValue(YEAR, 4) // 2 digit year not handled - .appendLiteral(' ') - .appendValue(HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(MINUTE_OF_HOUR, 2) - .optionalStart() - .appendLiteral(':') - .appendValue(SECOND_OF_MINUTE, 2) - .optionalEnd() - .appendLiteral(' ') - .appendOffset("+HHMM", "GMT") // should handle UT/Z/EST/EDT/CST/CDT/MST/MDT/PST/MDT - .toFormatter(); + .parseCaseInsensitive() + .parseLenient() + .optionalStart() + .appendText(DAY_OF_WEEK, dow) + .appendLiteral(", ") + .optionalEnd() + .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) + .appendLiteral(' ') + .appendText(MONTH_OF_YEAR, moy) + .appendLiteral(' ') + .appendValue(YEAR, 4) // 2 digit year not handled + .appendLiteral(' ') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalEnd() + .appendLiteral(' ') + .appendOffset("+HHMM", "GMT") // should handle UT/Z/EST/EDT/CST/CDT/MST/MDT/PST/MDT + .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } /** @@ -922,14 +1207,20 @@ * @param printerParser the printer/parser to use, not null * @param locale the locale to use, not null * @param symbols the symbols to use, not null + * @param resolverStyle the resolver style to use, not null + * @param resolverFields the fields to use during resolving, null for all fields * @param chrono the chronology to use, null for no override * @param zone the zone to use, null for no override */ - DateTimeFormatter(CompositePrinterParser printerParser, Locale locale, - DateTimeFormatSymbols symbols, Chronology chrono, ZoneId zone) { + DateTimeFormatter(CompositePrinterParser printerParser, + Locale locale, DateTimeFormatSymbols symbols, + ResolverStyle resolverStyle, Set resolverFields, + Chronology chrono, ZoneId zone) { this.printerParser = Objects.requireNonNull(printerParser, "printerParser"); + this.resolverFields = resolverFields; this.locale = Objects.requireNonNull(locale, "locale"); this.symbols = Objects.requireNonNull(symbols, "symbols"); + this.resolverStyle = Objects.requireNonNull(resolverStyle, "resolverStyle"); this.chrono = chrono; this.zone = zone; } @@ -962,7 +1253,7 @@ if (this.locale.equals(locale)) { return this; } - return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + return new DateTimeFormatter(printerParser, locale, symbols, resolverStyle, resolverFields, chrono, zone); } //----------------------------------------------------------------------- @@ -987,7 +1278,7 @@ if (this.symbols.equals(symbols)) { return this; } - return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + return new DateTimeFormatter(printerParser, locale, symbols, resolverStyle, resolverFields, chrono, zone); } //----------------------------------------------------------------------- @@ -998,7 +1289,7 @@ * By default, a formatter has no override chronology, returning null. * See {@link #withChronology(Chronology)} for more details on overriding. * - * @return the chronology of this formatter, null if no override + * @return the override chronology of this formatter, null if no override */ public Chronology getChronology() { return chrono; @@ -1013,26 +1304,35 @@ *

    * If an override is added, then any date that is formatted or parsed will be affected. *

    - * When formatting, if the {@code Temporal} object contains a date then it will + * When formatting, if the temporal object contains a date, then it will * be converted to a date in the override chronology. - * Any time or zone will be retained unless overridden. - * The converted result will behave in a manner equivalent to an implementation - * of {@code ChronoLocalDate},{@code ChronoLocalDateTime} or {@code ChronoZonedDateTime}. + * Whether the temporal contains a date is determined by querying the + * {@link ChronoField#EPOCH_DAY EPOCH_DAY} field. + * Any time or zone will be retained unaltered unless overridden. *

    - * When parsing, the override chronology will be used to interpret the - * {@link java.time.temporal.ChronoField fields} into a date unless the - * formatter directly parses a valid chronology. + * If the temporal object does not contain a date, but does contain one + * or more {@code ChronoField} date fields, then a {@code DateTimeException} + * is thrown. In all other cases, the override chronology is added to the temporal, + * replacing any previous chronology, but without changing the date/time. + *

    + * When parsing, there are two distinct cases to consider. + * If a chronology has been parsed directly from the text, perhaps because + * {@link DateTimeFormatterBuilder#appendChronologyId()} was used, then + * this override chronology has no effect. + * If no zone has been parsed, then this override chronology will be used + * to interpret the {@code ChronoField} values into a date according to the + * date resolving rules of the chronology. *

    * This instance is immutable and unaffected by this method call. * - * @param chrono the new chronology, not null + * @param chrono the new chronology, null if no override * @return a formatter based on this formatter with the requested override chronology, not null */ public DateTimeFormatter withChronology(Chronology chrono) { if (Objects.equals(this.chrono, chrono)) { return this; } - return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + return new DateTimeFormatter(printerParser, locale, symbols, resolverStyle, resolverFields, chrono, zone); } //----------------------------------------------------------------------- @@ -1043,7 +1343,7 @@ * By default, a formatter has no override zone, returning null. * See {@link #withZone(ZoneId)} for more details on overriding. * - * @return the chronology of this formatter, null if no override + * @return the override zone of this formatter, null if no override */ public ZoneId getZone() { return zone; @@ -1058,28 +1358,192 @@ *

    * If an override is added, then any instant that is formatted or parsed will be affected. *

    - * When formatting, if the {@code Temporal} object contains an instant then it will + * When formatting, if the temporal object contains an instant, then it will * be converted to a zoned date-time using the override zone. + * Whether the temporal is an instant is determined by querying the + * {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} field. * If the input has a chronology then it will be retained unless overridden. * If the input does not have a chronology, such as {@code Instant}, then * the ISO chronology will be used. - * The converted result will behave in a manner equivalent to an implementation - * of {@code ChronoZonedDateTime}. *

    - * When parsing, the override zone will be used to interpret the - * {@link java.time.temporal.ChronoField fields} into an instant unless the - * formatter directly parses a valid zone. + * If the temporal object does not contain an instant, but does contain + * an offset then an additional check is made. If the normalized override + * zone is an offset that differs from the offset of the temporal, then + * a {@code DateTimeException} is thrown. In all other cases, the override + * zone is added to the temporal, replacing any previous zone, but without + * changing the date/time. + *

    + * When parsing, there are two distinct cases to consider. + * If a zone has been parsed directly from the text, perhaps because + * {@link DateTimeFormatterBuilder#appendZoneId()} was used, then + * this override zone has no effect. + * If no zone has been parsed, then this override zone will be included in + * the result of the parse where it can be used to build instants and date-times. *

    * This instance is immutable and unaffected by this method call. * - * @param zone the new override zone, not null + * @param zone the new override zone, null if no override * @return a formatter based on this formatter with the requested override zone, not null */ public DateTimeFormatter withZone(ZoneId zone) { if (Objects.equals(this.zone, zone)) { return this; } - return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + return new DateTimeFormatter(printerParser, locale, symbols, resolverStyle, resolverFields, chrono, zone); + } + + //----------------------------------------------------------------------- + /** + * Gets the resolver style to use during parsing. + *

    + * This returns the resolver style, used during the second phase of parsing + * when fields are resolved into dates and times. + * By default, a formatter has the {@link ResolverStyle#SMART SMART} resolver style. + * See {@link #withResolverStyle(ResolverStyle)} for more details. + * + * @return the resolver style of this formatter, not null + */ + public ResolverStyle getResolverStyle() { + return resolverStyle; + } + + /** + * Returns a copy of this formatter with a new resolver style. + *

    + * This returns a formatter with similar state to this formatter but + * with the resolver style set. By default, a formatter has the + * {@link ResolverStyle#SMART SMART} resolver style. + *

    + * Changing the resolver style only has an effect during parsing. + * Parsing a text string occurs in two phases. + * Phase 1 is a basic text parse according to the fields added to the builder. + * Phase 2 resolves the parsed field-value pairs into date and/or time objects. + * The resolver style is used to control how phase 2, resolving, happens. + * See {@code ResolverStyle} for more information on the options available. + *

    + * This instance is immutable and unaffected by this method call. + * + * @param resolverStyle the new resolver style, not null + * @return a formatter based on this formatter with the requested resolver style, not null + */ + public DateTimeFormatter withResolverStyle(ResolverStyle resolverStyle) { + Objects.requireNonNull(resolverStyle, "resolverStyle"); + if (Objects.equals(this.resolverStyle, resolverStyle)) { + return this; + } + return new DateTimeFormatter(printerParser, locale, symbols, resolverStyle, resolverFields, chrono, zone); + } + + //----------------------------------------------------------------------- + /** + * Gets the resolver fields to use during parsing. + *

    + * This returns the resolver fields, used during the second phase of parsing + * when fields are resolved into dates and times. + * By default, a formatter has no resolver fields, and thus returns null. + * See {@link #withResolverFields(Set)} for more details. + * + * @return the immutable set of resolver fields of this formatter, null if no fields + */ + public Set getResolverFields() { + return resolverFields; + } + + /** + * Returns a copy of this formatter with a new set of resolver fields. + *

    + * This returns a formatter with similar state to this formatter but with + * the resolver fields set. By default, a formatter has no resolver fields. + *

    + * Changing the resolver fields only has an effect during parsing. + * Parsing a text string occurs in two phases. + * Phase 1 is a basic text parse according to the fields added to the builder. + * Phase 2 resolves the parsed field-value pairs into date and/or time objects. + * The resolver fields are used to filter the field-value pairs between phase 1 and 2. + *

    + * This can be used to select between two or more ways that a date or time might + * be resolved. For example, if the formatter consists of year, month, day-of-month + * and day-of-year, then there are two ways to resolve a date. + * Calling this method with the arguments {@link ChronoField#YEAR YEAR} and + * {@link ChronoField#DAY_OF_YEAR DAY_OF_YEAR} will ensure that the date is + * resolved using the year and day-of-year, effectively meaning that the month + * and day-of-month are ignored during the resolving phase. + *

    + * In a similar manner, this method can be used to ignore secondary fields that + * would otherwise be cross-checked. For example, if the formatter consists of year, + * month, day-of-month and day-of-week, then there is only one way to resolve a + * date, but the parsed value for day-of-week will be cross-checked against the + * resolved date. Calling this method with the arguments {@link ChronoField#YEAR YEAR}, + * {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} and + * {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} will ensure that the date is + * resolved correctly, but without any cross-check for the day-of-week. + *

    + * In implementation terms, this method behaves as follows. The result of the + * parsing phase can be considered to be a map of field to value. The behavior + * of this method is to cause that map to be filtered between phase 1 and 2, + * removing all fields other than those specified as arguments to this method. + *

    + * This instance is immutable and unaffected by this method call. + * + * @param resolverFields the new set of resolver fields, null if no fields + * @return a formatter based on this formatter with the requested resolver style, not null + */ + public DateTimeFormatter withResolverFields(TemporalField... resolverFields) { + Objects.requireNonNull(resolverFields, "resolverFields"); + Set fields = new HashSet<>(Arrays.asList(resolverFields)); + if (Objects.equals(this.resolverFields, fields)) { + return this; + } + fields = Collections.unmodifiableSet(fields); + return new DateTimeFormatter(printerParser, locale, symbols, resolverStyle, fields, chrono, zone); + } + + /** + * Returns a copy of this formatter with a new set of resolver fields. + *

    + * This returns a formatter with similar state to this formatter but with + * the resolver fields set. By default, a formatter has no resolver fields. + *

    + * Changing the resolver fields only has an effect during parsing. + * Parsing a text string occurs in two phases. + * Phase 1 is a basic text parse according to the fields added to the builder. + * Phase 2 resolves the parsed field-value pairs into date and/or time objects. + * The resolver fields are used to filter the field-value pairs between phase 1 and 2. + *

    + * This can be used to select between two or more ways that a date or time might + * be resolved. For example, if the formatter consists of year, month, day-of-month + * and day-of-year, then there are two ways to resolve a date. + * Calling this method with the arguments {@link ChronoField#YEAR YEAR} and + * {@link ChronoField#DAY_OF_YEAR DAY_OF_YEAR} will ensure that the date is + * resolved using the year and day-of-year, effectively meaning that the month + * and day-of-month are ignored during the resolving phase. + *

    + * In a similar manner, this method can be used to ignore secondary fields that + * would otherwise be cross-checked. For example, if the formatter consists of year, + * month, day-of-month and day-of-week, then there is only one way to resolve a + * date, but the parsed value for day-of-week will be cross-checked against the + * resolved date. Calling this method with the arguments {@link ChronoField#YEAR YEAR}, + * {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} and + * {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} will ensure that the date is + * resolved correctly, but without any cross-check for the day-of-week. + *

    + * In implementation terms, this method behaves as follows. The result of the + * parsing phase can be considered to be a map of field to value. The behavior + * of this method is to cause that map to be filtered between phase 1 and 2, + * removing all fields other than those specified as arguments to this method. + *

    + * This instance is immutable and unaffected by this method call. + * + * @param resolverFields the new set of resolver fields, null if no fields + * @return a formatter based on this formatter with the requested resolver style, not null + */ + public DateTimeFormatter withResolverFields(Set resolverFields) { + Objects.requireNonNull(resolverFields, "resolverFields"); + if (Objects.equals(this.resolverFields, resolverFields)) { + return this; + } + resolverFields = Collections.unmodifiableSet(new HashSet<>(resolverFields)); + return new DateTimeFormatter(printerParser, locale, symbols, resolverStyle, resolverFields, chrono, zone); } //----------------------------------------------------------------------- @@ -1151,7 +1615,7 @@ public TemporalAccessor parse(CharSequence text) { Objects.requireNonNull(text, "text"); try { - return parseToBuilder(text, null).resolve(); + return parseResolved0(text, null); } catch (DateTimeParseException ex) { throw ex; } catch (RuntimeException ex) { @@ -1193,7 +1657,7 @@ Objects.requireNonNull(text, "text"); Objects.requireNonNull(position, "position"); try { - return parseToBuilder(text, position).resolve(); + return parseResolved0(text, position); } catch (DateTimeParseException | IndexOutOfBoundsException ex) { throw ex; } catch (RuntimeException ex) { @@ -1225,8 +1689,7 @@ Objects.requireNonNull(text, "text"); Objects.requireNonNull(query, "query"); try { - DateTimeBuilder builder = parseToBuilder(text, null).resolve(); - return builder.query(query); + return parseResolved0(text, null).query(query); } catch (DateTimeParseException ex) { throw ex; } catch (RuntimeException ex) { @@ -1238,7 +1701,7 @@ * Fully parses the text producing an object of one of the specified types. *

    * This parse method is convenient for use when the parser can handle optional elements. - * For example, a pattern of 'yyyy-MM-dd HH.mm[Z]]' can be fully parsed to a {@code ZonedDateTime}, + * For example, a pattern of 'uuuu-MM-dd HH.mm[ VV]' can be fully parsed to a {@code ZonedDateTime}, * or partially parsed to a {@code LocalDateTime}. * The queries must be specified in order, starting from the best matching full-parse option * and ending with the worst matching minimal parse option. @@ -1272,10 +1735,10 @@ throw new IllegalArgumentException("At least two queries must be specified"); } try { - DateTimeBuilder builder = parseToBuilder(text, null).resolve(); + TemporalAccessor resolved = parseResolved0(text, null); for (TemporalQuery query : queries) { try { - return (TemporalAccessor) builder.query(query); + return (TemporalAccessor) resolved.query(query); } catch (RuntimeException ex) { // continue } @@ -1289,7 +1752,7 @@ } private DateTimeParseException createError(CharSequence text, RuntimeException ex) { - String abbr = ""; + String abbr; if (text.length() > 64) { abbr = text.subSequence(0, 64).toString() + "..."; } else { @@ -1300,23 +1763,23 @@ //----------------------------------------------------------------------- /** - * Parses the text to a builder. + * Parses and resolves the specified text. *

    - * This parses to a {@code DateTimeBuilder} ensuring that the text is fully parsed. - * This method throws {@link DateTimeParseException} if unable to parse, or - * some other {@code DateTimeException} if another date/time problem occurs. + * This parses to a {@code TemporalAccessor} ensuring that the text is fully parsed. * * @param text the text to parse, not null * @param position the position to parse from, updated with length parsed * and the index of any error, null if parsing whole string - * @return the engine representing the result of the parse, not null + * @return the resolved result of the parse, not null * @throws DateTimeParseException if the parse fails + * @throws DateTimeException if an error occurs while resolving the date or time + * @throws IndexOutOfBoundsException if the position is invalid */ - private DateTimeBuilder parseToBuilder(final CharSequence text, final ParsePosition position) { + private TemporalAccessor parseResolved0(final CharSequence text, final ParsePosition position) { ParsePosition pos = (position != null ? position : new ParsePosition(0)); - DateTimeParseContext result = parseUnresolved0(text, pos); - if (result == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) { - String abbr = ""; + Parsed unresolved = parseUnresolved0(text, pos); + if (unresolved == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) { + String abbr; if (text.length() > 64) { abbr = text.subSequence(0, 64).toString() + "..."; } else { @@ -1330,7 +1793,7 @@ pos.getIndex(), text, pos.getIndex()); } } - return result.resolveFields().toBuilder(); + return unresolved.resolve(resolverStyle, resolverFields); } /** @@ -1376,7 +1839,7 @@ return parseUnresolved0(text, position); } - private DateTimeParseContext parseUnresolved0(CharSequence text, ParsePosition position) { + private Parsed parseUnresolved0(CharSequence text, ParsePosition position) { Objects.requireNonNull(text, "text"); Objects.requireNonNull(position, "position"); DateTimeParseContext context = new DateTimeParseContext(this); @@ -1387,7 +1850,7 @@ return null; } position.setIndex(pos); // errorIndex not updated from input - return context; + return context.toParsed(); } //----------------------------------------------------------------------- @@ -1496,7 +1959,7 @@ Objects.requireNonNull(text, "text"); try { if (parseType == null) { - return formatter.parseToBuilder(text, null).resolve(); + return formatter.parseResolved0(text, null); } return formatter.parse(text, parseType); } catch (DateTimeParseException ex) { @@ -1508,7 +1971,7 @@ @Override public Object parseObject(String text, ParsePosition pos) { Objects.requireNonNull(text, "text"); - DateTimeParseContext unresolved; + Parsed unresolved; try { unresolved = formatter.parseUnresolved0(text, pos); } catch (IndexOutOfBoundsException ex) { @@ -1524,11 +1987,11 @@ return null; } try { - DateTimeBuilder builder = unresolved.resolveFields().toBuilder().resolve(); + TemporalAccessor resolved = unresolved.resolve(formatter.resolverStyle, formatter.resolverFields); if (parseType == null) { - return builder; + return resolved; } - return builder.query(parseType); + return resolved.query(parseType); } catch (RuntimeException ex) { pos.setErrorIndex(0); return null; diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/DateTimeFormatterBuilder.java --- a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Sat Apr 13 21:51:36 2013 +0100 @@ -83,11 +83,9 @@ import java.time.ZoneOffset; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; -import java.time.chrono.JapaneseChronology; import java.time.format.DateTimeTextProvider.LocaleStore; import java.time.temporal.ChronoField; import java.time.temporal.IsoFields; -import java.time.temporal.Queries; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; @@ -111,7 +109,10 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.LocaleResources; import sun.util.locale.provider.TimeZoneNameUtility; /** @@ -129,6 +130,8 @@ *

  • OffsetId/Offset - the {@linkplain ZoneOffset zone offset}
  • *
  • ZoneId - the {@linkplain ZoneId time-zone} id
  • *
  • ZoneText - the name of the time-zone
  • + *
  • ChronologyId - the {@linkplain Chronology chronology} id
  • + *
  • ChronologyText - the name of the chronology
  • *
  • Literal - a text literal
  • *
  • Nested and Optional - formats can be nested or made optional
  • *
  • Other - the printer and parser interfaces can be used to add user supplied formatting
  • @@ -150,7 +153,7 @@ * Query for a time-zone that is region-only. */ private static final TemporalQuery QUERY_REGION_ONLY = (temporal) -> { - ZoneId zone = temporal.query(Queries.zoneId()); + ZoneId zone = temporal.query(TemporalQuery.zoneId()); return (zone != null && zone instanceof ZoneOffset == false ? zone : null); }; @@ -288,6 +291,40 @@ //----------------------------------------------------------------------- /** + * Appends a default value for a field to the formatter for use in parsing. + *

    + * This appends an instruction to the builder to inject a default value + * into the parsed result. This is especially useful in conjunction with + * optional parts of the formatter. + *

    + * For example, consider a formatter that parses the year, followed by + * an optional month, with a further optional day-of-month. Using such a + * formatter would require the calling code to check whether a full date, + * year-month or just a year had been parsed. This method can be used to + * default the month and day-of-month to a sensible value, such as the + * first of the month, allowing the calling code to always get a date. + *

    + * During formatting, this method has no effect. + *

    + * During parsing, the current state of the parse is inspected. + * If the specified field has no associated value, because it has not been + * parsed successfully at that point, then the specified value is injected + * into the parse result. Injection is immediate, thus the field-value pair + * will be visible to any subsequent elements in the formatter. + * As such, this method is normally called at the end of the builder. + * + * @param field the field to default the value of, not null + * @param value the value to default the field to + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) { + Objects.requireNonNull(field, "field"); + appendInternal(new DefaultValueParser(field, value)); + return this; + } + + //----------------------------------------------------------------------- + /** * Appends the value of a date-time field to the formatter using a normal * output style. *

    @@ -655,7 +692,7 @@ * This appends an instruction to format/parse the offset ID to the builder. *

    * During formatting, the offset is obtained using a mechanism equivalent - * to querying the temporal with {@link Queries#offset()}. + * to querying the temporal with {@link TemporalQuery#offset()}. * It will be printed using the format defined below. * If the offset cannot be obtained then an exception is thrown unless the * section of the formatter is optional. @@ -692,6 +729,44 @@ return this; } + /** + * Appends the localized zone offset, such as 'GMT+01:00', to the formatter. + *

    + * This appends a localized zone offset to the builder, the format of the + * localized offset is controlled by the specified {@link FormatStyle style} + * to this method: + *

      + *
    • {@link TextStyle#FULL full} - formats with localized offset text, such + * as 'GMT, 2-digit hour and minute field, optional second field if non-zero, + * and colon. + *
    • {@link TextStyle#SHORT short} - formats with localized offset text, + * such as 'GMT, hour without leading zero, optional 2-digit minute and + * second if non-zero, and colon. + *

    + *

    + * During formatting, the offset is obtained using a mechanism equivalent + * to querying the temporal with {@link TemporalQuery#offset()}. + * If the offset cannot be obtained then an exception is thrown unless the + * section of the formatter is optional. + *

    + * During parsing, the offset is parsed using the format defined above. + * If the offset cannot be parsed then an exception is thrown unless the + * section of the formatter is optional. + *

    + * @param style the format style to use, not null + * @return this, for chaining, not null + * @throws IllegalArgumentException if style is neither {@link TextStyle#FULL + * full} nor {@link TextStyle#SHORT short} + */ + public DateTimeFormatterBuilder appendLocalizedOffset(TextStyle style) { + Objects.requireNonNull(style, "style"); + if (style != TextStyle.FULL && style != TextStyle.SHORT) { + throw new IllegalArgumentException("Style must be either full or short"); + } + appendInternal(new LocalizedOffsetIdPrinterParser(style)); + return this; + } + //----------------------------------------------------------------------- /** * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. @@ -702,7 +777,7 @@ * for use with this method, see {@link #appendZoneOrOffsetId()}. *

    * During formatting, the zone is obtained using a mechanism equivalent - * to querying the temporal with {@link Queries#zoneId()}. + * to querying the temporal with {@link TemporalQuery#zoneId()}. * It will be printed using the result of {@link ZoneId#getId()}. * If the zone cannot be obtained then an exception is thrown unless the * section of the formatter is optional. @@ -725,25 +800,25 @@ *

    * For example, the following will parse: *

    -     *   "Europe/London"           -> ZoneId.of("Europe/London")
    -     *   "Z"                       -> ZoneOffset.UTC
    -     *   "UT"                      -> ZoneOffset.UTC
    -     *   "UTC"                     -> ZoneOffset.UTC
    -     *   "GMT"                     -> ZoneOffset.UTC
    -     *   "UT0"                     -> ZoneOffset.UTC
    -     *   "UTC0"                    -> ZoneOffset.UTC
    -     *   "GMT0"                    -> ZoneOffset.UTC
    -     *   "+01:30"                  -> ZoneOffset.of("+01:30")
    -     *   "UT+01:30"                -> ZoneOffset.of("+01:30")
    -     *   "UTC+01:30"               -> ZoneOffset.of("+01:30")
    -     *   "GMT+01:30"               -> ZoneOffset.of("+01:30")
    +     *   "Europe/London"           -- ZoneId.of("Europe/London")
    +     *   "Z"                       -- ZoneOffset.UTC
    +     *   "UT"                      -- ZoneOffset.UTC
    +     *   "UTC"                     -- ZoneOffset.UTC
    +     *   "GMT"                     -- ZoneOffset.UTC
    +     *   "UT0"                     -- ZoneOffset.UTC
    +     *   "UTC0"                    -- ZoneOffset.UTC
    +     *   "GMT0"                    -- ZoneOffset.UTC
    +     *   "+01:30"                  -- ZoneOffset.of("+01:30")
    +     *   "UT+01:30"                -- ZoneOffset.of("+01:30")
    +     *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
    +     *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
          * 
    * * @return this, for chaining, not null * @see #appendZoneRegionId() */ public DateTimeFormatterBuilder appendZoneId() { - appendInternal(new ZoneIdPrinterParser(Queries.zoneId(), "ZoneId()")); + appendInternal(new ZoneIdPrinterParser(TemporalQuery.zoneId(), "ZoneId()")); return this; } @@ -755,7 +830,7 @@ * only if it is a region-based ID. *

    * During formatting, the zone is obtained using a mechanism equivalent - * to querying the temporal with {@link Queries#zoneId()}. + * to querying the temporal with {@link TemporalQuery#zoneId()}. * If the zone is a {@code ZoneOffset} or it cannot be obtained then * an exception is thrown unless the section of the formatter is optional. * If the zone is not an offset, then the zone will be printed using @@ -779,18 +854,18 @@ *

    * For example, the following will parse: *

    -     *   "Europe/London"           -> ZoneId.of("Europe/London")
    -     *   "Z"                       -> ZoneOffset.UTC
    -     *   "UT"                      -> ZoneOffset.UTC
    -     *   "UTC"                     -> ZoneOffset.UTC
    -     *   "GMT"                     -> ZoneOffset.UTC
    -     *   "UT0"                     -> ZoneOffset.UTC
    -     *   "UTC0"                    -> ZoneOffset.UTC
    -     *   "GMT0"                    -> ZoneOffset.UTC
    -     *   "+01:30"                  -> ZoneOffset.of("+01:30")
    -     *   "UT+01:30"                -> ZoneOffset.of("+01:30")
    -     *   "UTC+01:30"               -> ZoneOffset.of("+01:30")
    -     *   "GMT+01:30"               -> ZoneOffset.of("+01:30")
    +     *   "Europe/London"           -- ZoneId.of("Europe/London")
    +     *   "Z"                       -- ZoneOffset.UTC
    +     *   "UT"                      -- ZoneOffset.UTC
    +     *   "UTC"                     -- ZoneOffset.UTC
    +     *   "GMT"                     -- ZoneOffset.UTC
    +     *   "UT0"                     -- ZoneOffset.UTC
    +     *   "UTC0"                    -- ZoneOffset.UTC
    +     *   "GMT0"                    -- ZoneOffset.UTC
    +     *   "+01:30"                  -- ZoneOffset.of("+01:30")
    +     *   "UT+01:30"                -- ZoneOffset.of("+01:30")
    +     *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
    +     *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
          * 
    *

    * Note that this method is is identical to {@code appendZoneId()} except @@ -817,7 +892,7 @@ * then attempts to find an offset, such as that on {@code OffsetDateTime}. *

    * During formatting, the zone is obtained using a mechanism equivalent - * to querying the temporal with {@link Queries#zone()}. + * to querying the temporal with {@link TemporalQuery#zone()}. * It will be printed using the result of {@link ZoneId#getId()}. * If the zone cannot be obtained then an exception is thrown unless the * section of the formatter is optional. @@ -840,18 +915,18 @@ *

    * For example, the following will parse: *

    -     *   "Europe/London"           -> ZoneId.of("Europe/London")
    -     *   "Z"                       -> ZoneOffset.UTC
    -     *   "UT"                      -> ZoneOffset.UTC
    -     *   "UTC"                     -> ZoneOffset.UTC
    -     *   "GMT"                     -> ZoneOffset.UTC
    -     *   "UT0"                     -> ZoneOffset.UTC
    -     *   "UTC0"                    -> ZoneOffset.UTC
    -     *   "GMT0"                    -> ZoneOffset.UTC
    -     *   "+01:30"                  -> ZoneOffset.of("+01:30")
    -     *   "UT+01:30"                -> ZoneOffset.of("+01:30")
    -     *   "UTC+01:30"               -> ZoneOffset.of("+01:30")
    -     *   "GMT+01:30"               -> ZoneOffset.of("+01:30")
    +     *   "Europe/London"           -- ZoneId.of("Europe/London")
    +     *   "Z"                       -- ZoneOffset.UTC
    +     *   "UT"                      -- ZoneOffset.UTC
    +     *   "UTC"                     -- ZoneOffset.UTC
    +     *   "GMT"                     -- ZoneOffset.UTC
    +     *   "UT0"                     -- ZoneOffset.UTC
    +     *   "UTC0"                    -- ZoneOffset.UTC
    +     *   "GMT0"                    -- ZoneOffset.UTC
    +     *   "+01:30"                  -- ZoneOffset.of("+01:30")
    +     *   "UT+01:30"                -- ZoneOffset.of("+01:30")
    +     *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
    +     *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
          * 
    *

    * Note that this method is is identical to {@code appendZoneId()} except @@ -861,7 +936,7 @@ * @see #appendZoneId() */ public DateTimeFormatterBuilder appendZoneOrOffsetId() { - appendInternal(new ZoneIdPrinterParser(Queries.zone(), "ZoneOrOffsetId()")); + appendInternal(new ZoneIdPrinterParser(TemporalQuery.zone(), "ZoneOrOffsetId()")); return this; } @@ -872,7 +947,7 @@ * the builder. *

    * During formatting, the zone is obtained using a mechanism equivalent - * to querying the temporal with {@link Queries#zoneId()}. + * to querying the temporal with {@link TemporalQuery#zoneId()}. * If the zone is a {@code ZoneOffset} it will be printed using the * result of {@link ZoneOffset#getId()}. * If the zone is not an offset, the textual name will be looked up @@ -908,7 +983,7 @@ * the builder. *

    * During formatting, the zone is obtained using a mechanism equivalent - * to querying the temporal with {@link Queries#zoneId()}. + * to querying the temporal with {@link TemporalQuery#zoneId()}. * If the zone is a {@code ZoneOffset} it will be printed using the * result of {@link ZoneOffset#getId()}. * If the zone is not an offset, the textual name will be looked up @@ -951,7 +1026,7 @@ * This appends an instruction to format/parse the chronology ID to the builder. *

    * During formatting, the chronology is obtained using a mechanism equivalent - * to querying the temporal with {@link Queries#chronology()}. + * to querying the temporal with {@link TemporalQuery#chronology()}. * It will be printed using the result of {@link Chronology#getId()}. * If the chronology cannot be obtained then an exception is thrown unless the * section of the formatter is optional. @@ -1098,24 +1173,25 @@ * Appends the elements defined by the specified pattern to the builder. *

    * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. - * The characters '{' and '}' are reserved for future use. + * The characters '#', '{' and '}' are reserved for future use. * The characters '[' and ']' indicate optional patterns. * The following pattern letters are defined: *

          *  Symbol  Meaning                     Presentation      Examples
          *  ------  -------                     ------------      -------
    -     *   G       era                         text              A; AD; Anno Domini
    -     *   y       year                        year              2004; 04
    +     *   G       era                         text              AD; Anno Domini; A
    +     *   u       year                        year              2004; 04
    +     *   y       year-of-era                 year              2004; 04
          *   D       day-of-year                 number            189
    -     *   M       month-of-year               number/text       7; 07; Jul; July; J
    +     *   M/L     month-of-year               number/text       7; 07; Jul; July; J
          *   d       day-of-month                number            10
          *
    -     *   Q       quarter-of-year             number/text       3; 03; Q3
    +     *   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
          *   Y       week-based-year             year              1996; 96
    -     *   w       week-of-year                number            27
    -     *   W       week-of-month               number            27
    -     *   e       localized day-of-week       number            2; Tue; Tuesday; T
    -     *   E       day-of-week                 number/text       2; Tue; Tuesday; T
    +     *   w       week-of-week-based-year     number            27
    +     *   W       week-of-month               number            4
    +     *   E       day-of-week                 text              Tue; Tuesday; T
    +     *   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
          *   F       week-of-month               number            3
          *
          *   a       am-pm-of-day                text              PM
    @@ -1133,6 +1209,7 @@
          *
          *   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
          *   z       time-zone name              zone-name         Pacific Standard Time; PST
    +     *   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
          *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
          *   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
          *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
    @@ -1143,116 +1220,169 @@
          *   ''      single quote                literal           '
          *   [       optional section start
          *   ]       optional section end
    -     *   {}      reserved for future use
    +     *   #       reserved for future use
    +     *   {       reserved for future use
    +     *   }       reserved for future use
          * 
    *

    * The count of pattern letters determine the format. - *

    - * Text: The text style is determined based on the number of pattern letters used. - * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}. - * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}. - * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}. - *

    - * Number: If the count of letters is one, then the value is printed using the minimum number - * of digits and without padding as per {@link #appendValue(java.time.temporal.TemporalField)}. Otherwise, the - * count of digits is used as the width of the output field as per {@link #appendValue(java.time.temporal.TemporalField, int)}. - *

    - * Number/Text: If the count of pattern letters is 3 or greater, use the Text rules above. - * Otherwise use the Number rules above. - *

    - * Fraction: Outputs the nano-of-second field as a fraction-of-second. - * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9. - * If it is less than 9, then the nano-of-second value is truncated, with only the most - * significant digits being output. - * When parsing in strict mode, the number of parsed digits must match the count of pattern letters. - * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern - * letters, up to 9 digits. + * See DateTimeFormatter for a user-focused description of the patterns. + * The following tables define how the pattern letters map to the builder. *

    - * Year: The count of letters determines the minimum field width below which padding is used. - * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used. - * For formatting, this outputs the rightmost two digits. For parsing, this will parse using the - * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. - * If the count of letters is less than four (but not two), then the sign is only output for negative - * years as per {@link SignStyle#NORMAL}. - * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD} - *

    - * ZoneId: This outputs the time-zone ID, such as 'Europe/Paris'. - * If the count of letters is two, then the time-zone ID is output. - * Any other count of letters throws {@code IllegalArgumentException}. + * Date fields: Pattern letters to output a date. *

    -     *  Pattern     Equivalent builder methods
    -     *   VV          appendZoneId()
    -     * 
    - *

    - * Zone names: This outputs the display name of the time-zone ID. - * If the count of letters is one, two or three, then the short name is output. - * If the count of letters is four, then the full name is output. - * Five or more letters throws {@code IllegalArgumentException}. - *

    -     *  Pattern     Equivalent builder methods
    -     *   z           appendZoneText(TextStyle.SHORT)
    -     *   zz          appendZoneText(TextStyle.SHORT)
    -     *   zzz         appendZoneText(TextStyle.SHORT)
    -     *   zzzz        appendZoneText(TextStyle.FULL)
    +     *  Pattern  Count  Equivalent builder methods
    +     *  -------  -----  --------------------------
    +     *    G       1      appendText(ChronoField.ERA, TextStyle.SHORT)
    +     *    GG      2      appendText(ChronoField.ERA, TextStyle.SHORT)
    +     *    GGG     3      appendText(ChronoField.ERA, TextStyle.SHORT)
    +     *    GGGG    4      appendText(ChronoField.ERA, TextStyle.FULL)
    +     *    GGGGG   5      appendText(ChronoField.ERA, TextStyle.NARROW)
    +     *
    +     *    u       1      appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL);
    +     *    uu      2      appendValueReduced(ChronoField.YEAR, 2, 2000);
    +     *    uuu     3      appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL);
    +     *    u..u    4..n   appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD);
    +     *    y       1      appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL);
    +     *    yy      2      appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000);
    +     *    yyy     3      appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL);
    +     *    y..y    4..n   appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD);
    +     *    Y       1      append special localized WeekFields element for numeric week-based-year
    +     *    YY      2      append special localized WeekFields element for reduced numeric week-based-year 2 digits;
    +     *    YYY     3      append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL);
    +     *    Y..Y    4..n   append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD);
    +     *
    +     *    Q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
    +     *    QQ      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
    +     *    QQQ     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT)
    +     *    QQQQ    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL)
    +     *    QQQQQ   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW)
    +     *    q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
    +     *    qq      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
    +     *    qqq     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE)
    +     *    qqqq    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE)
    +     *    qqqqq   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE)
    +     *
    +     *    M       1      appendValue(ChronoField.MONTH_OF_YEAR);
    +     *    MM      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
    +     *    MMM     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
    +     *    MMMM    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
    +     *    MMMMM   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW)
    +     *    L       1      appendValue(ChronoField.MONTH_OF_YEAR);
    +     *    LL      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
    +     *    LLL     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE)
    +     *    LLLL    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)
    +     *    LLLLL   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE)
    +     *
    +     *    w       1      append special localized WeekFields element for numeric week-of-year
    +     *    ww      1      append special localized WeekFields element for numeric week-of-year, zero-padded
    +     *    W       1      append special localized WeekFields element for numeric week-of-month
    +     *    d       1      appendValue(ChronoField.DAY_OF_MONTH)
    +     *    dd      2      appendValue(ChronoField.DAY_OF_MONTH, 2)
    +     *    D       1      appendValue(ChronoField.DAY_OF_YEAR)
    +     *    DD      2      appendValue(ChronoField.DAY_OF_YEAR, 2)
    +     *    DDD     3      appendValue(ChronoField.DAY_OF_YEAR, 3)
    +     *    F       1      appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
    +     *    E       1      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    +     *    EE      2      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    +     *    EEE     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    +     *    EEEE    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
    +     *    EEEEE   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
    +     *    e       1      append special localized WeekFields element for numeric day-of-week
    +     *    ee      2      append special localized WeekFields element for numeric day-of-week, zero-padded
    +     *    eee     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    +     *    eeee    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
    +     *    eeeee   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
    +     *    c       1      append special localized WeekFields element for numeric day-of-week
    +     *    ccc     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE)
    +     *    cccc    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE)
    +     *    ccccc   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE)
          * 
    *

    - * Offset X and x: This formats the offset based on the number of pattern letters. - * One letter outputs just the hour', such as '+01', unless the minute is non-zero - * in which case the minute is also output, such as '+0130'. - * Two letters outputs the hour and minute, without a colon, such as '+0130'. - * Three letters outputs the hour and minute, with a colon, such as '+01:30'. - * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'. - * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'. - * Six or more letters throws {@code IllegalArgumentException}. - * Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero, - * whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'. + * Time fields: Pattern letters to output a time. *

    -     *  Pattern     Equivalent builder methods
    -     *   X           appendOffset("+HHmm","Z")
    -     *   XX          appendOffset("+HHMM","Z")
    -     *   XXX         appendOffset("+HH:MM","Z")
    -     *   XXXX        appendOffset("+HHMMss","Z")
    -     *   XXXXX       appendOffset("+HH:MM:ss","Z")
    -     *   x           appendOffset("+HHmm","+00")
    -     *   xx          appendOffset("+HHMM","+0000")
    -     *   xxx         appendOffset("+HH:MM","+00:00")
    -     *   xxxx        appendOffset("+HHMMss","+0000")
    -     *   xxxxx       appendOffset("+HH:MM:ss","+00:00")
    +     *  Pattern  Count  Equivalent builder methods
    +     *  -------  -----  --------------------------
    +     *    a       1      appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
    +     *    h       1      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
    +     *    hh      2      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2)
    +     *    H       1      appendValue(ChronoField.HOUR_OF_DAY)
    +     *    HH      2      appendValue(ChronoField.HOUR_OF_DAY, 2)
    +     *    k       1      appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
    +     *    kk      2      appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2)
    +     *    K       1      appendValue(ChronoField.HOUR_OF_AMPM)
    +     *    KK      2      appendValue(ChronoField.HOUR_OF_AMPM, 2)
    +     *    m       1      appendValue(ChronoField.MINUTE_OF_HOUR)
    +     *    mm      2      appendValue(ChronoField.MINUTE_OF_HOUR, 2)
    +     *    s       1      appendValue(ChronoField.SECOND_OF_MINUTE)
    +     *    ss      2      appendValue(ChronoField.SECOND_OF_MINUTE, 2)
    +     *
    +     *    S..S    1..n   appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
    +     *    A       1      appendValue(ChronoField.MILLI_OF_DAY)
    +     *    A..A    2..n   appendValue(ChronoField.MILLI_OF_DAY, n)
    +     *    n       1      appendValue(ChronoField.NANO_OF_SECOND)
    +     *    n..n    2..n   appendValue(ChronoField.NANO_OF_SECOND, n)
    +     *    N       1      appendValue(ChronoField.NANO_OF_DAY)
    +     *    N..N    2..n   appendValue(ChronoField.NANO_OF_DAY, n)
    +     * 
    + *

    + * Zone ID: Pattern letters to output {@code ZoneId}. + *

    +     *  Pattern  Count  Equivalent builder methods
    +     *  -------  -----  --------------------------
    +     *    VV      2      appendZoneId()
    +     *    z       1      appendZoneText(TextStyle.SHORT)
    +     *    zz      2      appendZoneText(TextStyle.SHORT)
    +     *    zzz     3      appendZoneText(TextStyle.SHORT)
    +     *    zzzz    4      appendZoneText(TextStyle.FULL)
          * 
    *

    - * Offset Z: This formats the offset based on the number of pattern letters. - * One, two or three letters outputs the hour and minute, without a colon, such as '+0130'. - * Four or more letters throws {@code IllegalArgumentException}. - * The output will be '+0000' when the offset is zero. + * Zone offset: Pattern letters to output {@code ZoneOffset}. *

    -     *  Pattern     Equivalent builder methods
    -     *   Z           appendOffset("+HHMM","+0000")
    -     *   ZZ          appendOffset("+HHMM","+0000")
    -     *   ZZZ         appendOffset("+HHMM","+0000")
    +     *  Pattern  Count  Equivalent builder methods
    +     *  -------  -----  --------------------------
    +     *    O       1      appendLocalizedOffsetPrefixed(TextStyle.SHORT);
    +     *    OOOO    4      appendLocalizedOffsetPrefixed(TextStyle.FULL);
    +     *    X       1      appendOffset("+HHmm","Z")
    +     *    XX      2      appendOffset("+HHMM","Z")
    +     *    XXX     3      appendOffset("+HH:MM","Z")
    +     *    XXXX    4      appendOffset("+HHMMss","Z")
    +     *    XXXXX   5      appendOffset("+HH:MM:ss","Z")
    +     *    x       1      appendOffset("+HHmm","+00")
    +     *    xx      2      appendOffset("+HHMM","+0000")
    +     *    xxx     3      appendOffset("+HH:MM","+00:00")
    +     *    xxxx    4      appendOffset("+HHMMss","+0000")
    +     *    xxxxx   5      appendOffset("+HH:MM:ss","+00:00")
    +     *    Z       1      appendOffset("+HHMM","+0000")
    +     *    ZZ      2      appendOffset("+HHMM","+0000")
    +     *    ZZZ     3      appendOffset("+HHMM","+0000")
    +     *    ZZZZ    4      appendLocalizedOffset(TextStyle.FULL);
    +     *    ZZZZZ   5      appendOffset("+HH:MM:ss","Z")
          * 
    *

    - * Optional section: The optional section markers work exactly like calling {@link #optionalStart()} - * and {@link #optionalEnd()}. - *

    - * Pad modifier: Modifies the pattern that immediately follows to be padded with spaces. - * The pad width is determined by the number of pattern letters. - * This is the same as calling {@link #padNext(int)}. + * Modifiers: Pattern letters that modify the rest of the pattern: + *

    +     *  Pattern  Count  Equivalent builder methods
    +     *  -------  -----  --------------------------
    +     *    [       1      optionalStart()
    +     *    ]       1      optionalEnd()
    +     *    p..p    1..n   padNext(n)
    +     * 
    *

    - * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2. - *

    - * Any unrecognized letter is an error. - * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly. - * Despite this, it is recommended to use single quotes around all characters that you want to - * output directly to ensure that future changes do not break your application. + * Any sequence of letters not specified above, unrecognized letter or + * reserved character will throw an exception. + * Future versions may add to the set of patterns. + * It is recommended to use single quotes around all characters that you want + * to output directly to ensure that future changes do not break your application. *

    * Note that the pattern string is similar, but not identical, to * {@link java.text.SimpleDateFormat SimpleDateFormat}. * The pattern string is also similar, but not identical, to that defined by the * Unicode Common Locale Data Repository (CLDR/LDML). - * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric. - * Pattern letters 'X' is aligned with Unicode CLDR/LDML, which affects pattern 'X'. - * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently. - * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added. + * Pattern letters 'X' and 'u' are aligned with Unicode CLDR/LDML. + * By contrast, {@code SimpleDateFormat} uses 'u' for the numeric day of week. + * Pattern letters 'y' and 'Y' parse years of two digits and more than 4 digits differently. + * Pattern letters 'n', 'A', 'N', and 'p' are added. * Number types will reject large numbers. * * @param pattern the pattern to add, not null @@ -1308,10 +1438,23 @@ } appendZoneId(); } else if (cur == 'Z') { - if (count > 3) { + if (count < 4) { + appendOffset("+HHMM", "+0000"); + } else if (count == 4) { + appendLocalizedOffset(TextStyle.FULL); + } else if (count == 5) { + appendOffset("+HH:MM:ss","Z"); + } else { throw new IllegalArgumentException("Too many pattern letters: " + cur); } - appendOffset("+HHMM", "+0000"); + } else if (cur == 'O') { + if (count == 1) { + appendLocalizedOffset(TextStyle.SHORT); + } else if (count == 4) { + appendLocalizedOffset(TextStyle.FULL); + } else { + throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur); + } } else if (cur == 'X') { if (count > 5) { throw new IllegalArgumentException("Too many pattern letters: " + cur); @@ -1323,18 +1466,21 @@ } String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00")); appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero); - } else if (cur == 'w' || cur == 'e') { + } else if (cur == 'W') { // Fields defined by Locale if (count > 1) { throw new IllegalArgumentException("Too many pattern letters: " + cur); } appendInternal(new WeekBasedFieldPrinterParser(cur, count)); - } else if (cur == 'W') { + } else if (cur == 'w') { // Fields defined by Locale if (count > 2) { throw new IllegalArgumentException("Too many pattern letters: " + cur); } appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + } else if (cur == 'Y') { + // Fields defined by Locale + appendInternal(new WeekBasedFieldPrinterParser(cur, count)); } else { throw new IllegalArgumentException("Unknown pattern letter: " + cur); } @@ -1371,7 +1517,7 @@ } optionalEnd(); - } else if (cur == '{' || cur == '}') { + } else if (cur == '{' || cur == '}' || cur == '#') { throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'"); } else { appendLiteral(cur); @@ -1379,10 +1525,12 @@ } } + @SuppressWarnings("fallthrough") private void parseField(char cur, int count, TemporalField field) { + boolean standalone = false; switch (cur) { + case 'u': case 'y': - case 'Y': if (count == 2) { appendValueReduced(field, 2, 2000); } else if (count < 4) { @@ -1391,31 +1539,55 @@ appendValue(field, count, 19, SignStyle.EXCEEDS_PAD); } break; + case 'c': + if (count == 2) { + throw new IllegalArgumentException("Invalid pattern \"cc\""); + } + /*fallthrough*/ + case 'L': + case 'q': + standalone = true; + /*fallthrough*/ case 'M': case 'Q': case 'E': + case 'e': switch (count) { case 1: - appendValue(field); - break; case 2: - appendValue(field, 2); + if (cur == 'c' || cur == 'e') { + appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + } else if (cur == 'E') { + appendText(field, TextStyle.SHORT); + } else { + if (count == 1) { + appendValue(field); + } else { + appendValue(field, 2); + } + } break; case 3: - appendText(field, TextStyle.SHORT); + appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT); break; case 4: - appendText(field, TextStyle.FULL); + appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL); break; case 5: - appendText(field, TextStyle.NARROW); + appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW); break; default: throw new IllegalArgumentException("Too many pattern letters: " + cur); } break; + case 'a': + if (count == 1) { + appendText(field, TextStyle.SHORT); + } else { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + break; case 'G': - case 'a': switch (count) { case 1: case 2: @@ -1435,6 +1607,37 @@ case 'S': appendFraction(NANO_OF_SECOND, count, count, false); break; + case 'F': + if (count == 1) { + appendValue(field); + } else { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + break; + case 'd': + case 'h': + case 'H': + case 'k': + case 'K': + case 'm': + case 's': + if (count == 1) { + appendValue(field); + } else if (count == 2) { + appendValue(field, count); + } else { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + break; + case 'D': + if (count == 1) { + appendValue(field); + } else if (count <= 3) { + appendValue(field, count); + } else { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + break; default: if (count == 1) { appendValue(field); @@ -1448,44 +1651,43 @@ /** Map of letters to fields. */ private static final Map FIELD_MAP = new HashMap<>(); static { - FIELD_MAP.put('G', ChronoField.ERA); // Java, LDML (different to both for 1/2 chars) - FIELD_MAP.put('y', ChronoField.YEAR); // LDML - // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // Java, LDML // TODO redefine from above - // FIELD_MAP.put('u', ChronoField.YEAR); // LDML // TODO - // FIELD_MAP.put('Y', IsoFields.WEEK_BASED_YEAR); // Java7, LDML (needs localized week number) // TODO + // SDF = SimpleDateFormat + FIELD_MAP.put('G', ChronoField.ERA); // SDF, LDML (different to both for 1/2 chars) + FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // SDF, LDML + FIELD_MAP.put('u', ChronoField.YEAR); // LDML (different in SDF) FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310) - FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // Java, LDML - // FIELD_MAP.put('w', WeekFields.weekOfYear()); // Java, LDML (needs localized week number) - // FIELD_MAP.put('W', WeekFields.weekOfMonth()); // Java, LDML (needs localized week number) - FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // Java, LDML - FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // Java, LDML - FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH); // Java, LDML - FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // Java, LDML (different to both for 1/2 chars) - // FIELD_MAP.put('e', WeekFields.dayOfWeek()); // LDML (needs localized week number) - FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // Java, LDML - FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // Java, LDML - FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // Java, LDML - FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // Java, LDML - FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // Java, LDML - FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // Java, LDML - FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // Java, LDML - FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (Java uses milli-of-second number) + FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR); // LDML (stand-alone) + FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // SDF, LDML + FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR); // SDF, LDML (stand-alone) + FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // SDF, LDML + FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // SDF, LDML + FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH); // SDF, LDML + FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // SDF, LDML (different to both for 1/2 chars) + FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK); // LDML (stand-alone) + FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK); // LDML (needs localized week number) + FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // SDF, LDML + FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // SDF, LDML + FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // SDF, LDML + FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // SDF, LDML + FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // SDF, LDML + FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // SDF, LDML + FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // SDF, LDML + FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (SDF uses milli-of-second number) FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML) FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML) // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4 // 310 - Z - matches SimpleDateFormat and LDML - // 310 - V - time-zone id, matches proposed LDML + // 310 - V - time-zone id, matches LDML // 310 - p - prefix for padding - // 310 - X - matches proposed LDML, almost matches JavaSDF for 1, exact match 2&3, extended 4&5 - // 310 - x - matches proposed LDML - // Java - u - clashes with LDML, go with LDML (year-proleptic) here + // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5 + // 310 - x - matches LDML + // 310 - w, W, and Y are localized forms matching LDML // LDML - U - cycle year name, not supported by 310 yet // LDML - l - deprecated // LDML - j - not relevant // LDML - g - modified-julian-day // LDML - v,V - extended time-zone names - // LDML - q/c/L - standalone quarter/day-of-week/month } //----------------------------------------------------------------------- @@ -1632,10 +1834,12 @@ //----------------------------------------------------------------------- /** - * Completes this builder by creating the DateTimeFormatter using the default locale. + * Completes this builder by creating the {@code DateTimeFormatter} + * using the default locale. *

    - * This will create a formatter with the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. + * This will create a formatter with the {@linkplain Locale#getDefault(Locale.Category) default FORMAT locale}. * Numbers will be printed and parsed using the standard non-localized set of symbols. + * The resolver style will be {@link ResolverStyle#SMART SMART}. *

    * Calling this method will end any open optional sections by repeatedly * calling {@link #optionalEnd()} before creating the formatter. @@ -1650,10 +1854,12 @@ } /** - * Completes this builder by creating the DateTimeFormatter using the specified locale. + * Completes this builder by creating the {@code DateTimeFormatter} + * using the specified locale. *

    * This will create a formatter with the specified locale. * Numbers will be printed and parsed using the standard non-localized set of symbols. + * The resolver style will be {@link ResolverStyle#SMART SMART}. *

    * Calling this method will end any open optional sections by repeatedly * calling {@link #optionalEnd()} before creating the formatter. @@ -1665,12 +1871,35 @@ * @return the created formatter, not null */ public DateTimeFormatter toFormatter(Locale locale) { + return toFormatter(locale, ResolverStyle.SMART, null); + } + + /** + * Completes this builder by creating the formatter. + * This uses the default locale. + * + * @param resolverStyle the resolver style to use, not null + * @return the created formatter, not null + */ + DateTimeFormatter toFormatter(ResolverStyle resolverStyle, Chronology chrono) { + return toFormatter(Locale.getDefault(Locale.Category.FORMAT), resolverStyle, chrono); + } + + /** + * Completes this builder by creating the formatter. + * + * @param locale the locale to use for formatting, not null + * @param chrono the chronology to use, may be null + * @return the created formatter, not null + */ + private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle, Chronology chrono) { Objects.requireNonNull(locale, "locale"); while (active.parent != null) { optionalEnd(); } CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false); - return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null); + return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, + resolverStyle, null, chrono, null); } //----------------------------------------------------------------------- @@ -1942,6 +2171,31 @@ //----------------------------------------------------------------------- /** + * Defaults a value into the parse if not currently present. + */ + static class DefaultValueParser implements DateTimePrinterParser { + private final TemporalField field; + private final long value; + + DefaultValueParser(TemporalField field, long value) { + this.field = field; + this.value = value; + } + + public boolean format(DateTimePrintContext context, StringBuilder buf) { + return true; + } + + public int parse(DateTimeParseContext context, CharSequence text, int position) { + if (context.getParsed(field) == null) { + context.setParsedField(field, value, position, position); + } + return position; + } + } + + //----------------------------------------------------------------------- + /** * Prints or parses a character literal. */ static final class CharLiteralPrinterParser implements DateTimePrinterParser { @@ -2104,13 +2358,7 @@ @Override public boolean format(DateTimePrintContext context, StringBuilder buf) { - Chronology chrono = context.getTemporal().query(Queries.chronology()); - Long valueLong; - if (chrono == JapaneseChronology.INSTANCE && field == ChronoField.YEAR) { - valueLong = context.getValue(ChronoField.YEAR_OF_ERA); - } else { - valueLong = context.getValue(field); - } + Long valueLong = context.getValue(field); if (valueLong == null) { return false; } @@ -2281,14 +2529,7 @@ * @return the new position */ int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) { - TemporalField f = field; - if (field == ChronoField.YEAR) { - Chronology chrono = context.getEffectiveChronology(); - if (chrono == JapaneseChronology.INSTANCE) { - f = ChronoField.YEAR_OF_ERA; - } - } - return context.setParsedField(f, value, errorPos, successPos); + return context.setParsedField(field, value, errorPos, successPos); } @Override @@ -2570,7 +2811,7 @@ return false; } String text; - Chronology chrono = context.getTemporal().query(Queries.chronology()); + Chronology chrono = context.getTemporal().query(TemporalQuery.chronology()); if (chrono == null || chrono == IsoChronology.INSTANCE) { text = provider.getText(field, value, textStyle, context.getLocale()); } else { @@ -2887,6 +3128,167 @@ //----------------------------------------------------------------------- /** + * Prints or parses an offset ID. + */ + static final class LocalizedOffsetIdPrinterParser implements DateTimePrinterParser { + private final TextStyle style; + + /** + * Constructor. + * + * @param style the style, not null + */ + LocalizedOffsetIdPrinterParser(TextStyle style) { + this.style = style; + } + + private static StringBuilder appendHMS(StringBuilder buf, int t) { + return buf.append((char)(t / 10 + '0')) + .append((char)(t % 10 + '0')); + } + + @Override + public boolean format(DateTimePrintContext context, StringBuilder buf) { + Long offsetSecs = context.getValue(OFFSET_SECONDS); + if (offsetSecs == null) { + return false; + } + String gmtText = "GMT"; // TODO: get localized version of 'GMT' + if (gmtText != null) { + buf.append(gmtText); + } + int totalSecs = Math.toIntExact(offsetSecs); + if (totalSecs != 0) { + int absHours = Math.abs((totalSecs / 3600) % 100); // anything larger than 99 silently dropped + int absMinutes = Math.abs((totalSecs / 60) % 60); + int absSeconds = Math.abs(totalSecs % 60); + buf.append(totalSecs < 0 ? "-" : "+"); + if (style == TextStyle.FULL) { + appendHMS(buf, absHours); + buf.append(':'); + appendHMS(buf, absMinutes); + if (absSeconds != 0) { + buf.append(':'); + appendHMS(buf, absSeconds); + } + } else { + if (absHours >= 10) { + buf.append((char)(absHours / 10 + '0')); + } + buf.append((char)(absHours % 10 + '0')); + if (absMinutes != 0 || absSeconds != 0) { + buf.append(':'); + appendHMS(buf, absMinutes); + if (absSeconds != 0) { + buf.append(':'); + appendHMS(buf, absSeconds); + } + } + } + } + return true; + } + + int getDigit(CharSequence text, int position) { + char c = text.charAt(position); + if (c < '0' || c > '9') { + return -1; + } + return c - '0'; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + int pos = position; + int end = pos + text.length(); + String gmtText = "GMT"; // TODO: get localized version of 'GMT' + if (gmtText != null) { + if (!context.subSequenceEquals(text, pos, gmtText, 0, gmtText.length())) { + return ~position; + } + pos += gmtText.length(); + } + // parse normal plus/minus offset + int negative = 0; + if (pos == end) { + return context.setParsedField(OFFSET_SECONDS, 0, position, pos); + } + char sign = text.charAt(pos); // IOOBE if invalid position + if (sign == '+') { + negative = 1; + } else if (sign == '-') { + negative = -1; + } else { + return context.setParsedField(OFFSET_SECONDS, 0, position, pos); + } + pos++; + int h = 0; + int m = 0; + int s = 0; + if (style == TextStyle.FULL) { + int h1 = getDigit(text, pos++); + int h2 = getDigit(text, pos++); + if (h1 < 0 || h2 < 0 || text.charAt(pos++) != ':') { + return ~position; + } + h = h1 * 10 + h2; + int m1 = getDigit(text, pos++); + int m2 = getDigit(text, pos++); + if (m1 < 0 || m2 < 0) { + return ~position; + } + m = m1 * 10 + m2; + if (pos + 2 < end && text.charAt(pos) == ':') { + int s1 = getDigit(text, pos + 1); + int s2 = getDigit(text, pos + 2); + if (s1 >= 0 && s2 >= 0) { + s = s1 * 10 + s2; + pos += 3; + } + } + } else { + h = getDigit(text, pos++); + if (h < 0) { + return ~position; + } + if (pos < end) { + int h2 = getDigit(text, pos); + if (h2 >=0) { + h = h * 10 + h2; + pos++; + } + if (pos + 2 < end && text.charAt(pos) == ':') { + if (pos + 2 < end && text.charAt(pos) == ':') { + int m1 = getDigit(text, pos + 1); + int m2 = getDigit(text, pos + 2); + if (m1 >= 0 && m2 >= 0) { + m = m1 * 10 + m2; + pos += 3; + if (pos + 2 < end && text.charAt(pos) == ':') { + int s1 = getDigit(text, pos + 1); + int s2 = getDigit(text, pos + 2); + if (s1 >= 0 && s2 >= 0) { + s = s1 * 10 + s2; + pos += 3; + } + } + } + } + } + } + } + long offsetSecs = negative * (h * 3600L + m * 60L + s); + return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, pos); + } + + @Override + public String toString() { + return "LocalizedOffset(" + style + ")"; + } + } + + //----------------------------------------------------------------------- + /** * Prints or parses a zone ID. */ static final class ZoneTextPrinterParser extends ZoneIdPrinterParser { @@ -2898,7 +3300,7 @@ private Set preferredZones; ZoneTextPrinterParser(TextStyle textStyle, Set preferredZones) { - super(Queries.zone(), "ZoneText(" + textStyle + ")"); + super(TemporalQuery.zone(), "ZoneText(" + textStyle + ")"); this.textStyle = Objects.requireNonNull(textStyle, "textStyle"); if (preferredZones != null && preferredZones.size() != 0) { this.preferredZones = new HashSet<>(); @@ -2929,12 +3331,12 @@ } names = Arrays.copyOfRange(names, 0, 7); names[5] = - TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG,locale); + TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG, locale); if (names[5] == null) { names[5] = names[0]; // use the id } names[6] = - TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT,locale); + TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT, locale); if (names[6] == null) { names[6] = names[0]; } @@ -2946,16 +3348,16 @@ } switch (type) { case STD: - return names[textStyle.ordinal() + 1]; + return names[textStyle.zoneNameStyleIndex() + 1]; case DST: - return names[textStyle.ordinal() + 3]; + return names[textStyle.zoneNameStyleIndex() + 3]; } - return names[textStyle.ordinal() + 5]; + return names[textStyle.zoneNameStyleIndex() + 5]; } @Override public boolean format(DateTimePrintContext context, StringBuilder buf) { - ZoneId zone = context.getValue(Queries.zoneId()); + ZoneId zone = context.getValue(TemporalQuery.zoneId()); if (zone == null) { return false; } @@ -3507,14 +3909,14 @@ @Override public boolean format(DateTimePrintContext context, StringBuilder buf) { - Chronology chrono = context.getValue(Queries.chronology()); + Chronology chrono = context.getValue(TemporalQuery.chronology()); if (chrono == null) { return false; } if (textStyle == null) { buf.append(chrono.getId()); } else { - buf.append(chrono.getId()); // TODO: Use symbols + buf.append(getChronologyName(chrono, context.getLocale())); } return true; } @@ -3529,11 +3931,16 @@ Chronology bestMatch = null; int matchLen = -1; for (Chronology chrono : chronos) { - String id = chrono.getId(); - int idLen = id.length(); - if (idLen > matchLen && context.subSequenceEquals(text, position, id, 0, idLen)) { + String name; + if (textStyle == null) { + name = chrono.getId(); + } else { + name = getChronologyName(chrono, context.getLocale()); + } + int nameLen = name.length(); + if (nameLen > matchLen && context.subSequenceEquals(text, position, name, 0, nameLen)) { bestMatch = chrono; - matchLen = idLen; + matchLen = nameLen; } } if (bestMatch == null) { @@ -3542,6 +3949,22 @@ context.setParsed(bestMatch); return position + matchLen; } + + /** + * Returns the chronology name of the given chrono in the given locale + * if available, or the chronology Id otherwise. The regular ResourceBundle + * search path is used for looking up the chronology name. + * + * @param chrono the chronology, not null + * @param locale the locale, not null + * @return the chronology name of chrono in locale, or the id if no name is available + * @throws NullPointerException if chrono or locale is null + */ + private String getChronologyName(Chronology chrono, Locale locale) { + String key = "calendarname." + chrono.getCalendarType(); + String name = DateTimeTextProvider.getLocalizedResource(key, locale); + return name != null ? name : chrono.getId(); + } } //----------------------------------------------------------------------- @@ -3549,6 +3972,9 @@ * Prints or parses a localized pattern. */ static final class LocalizedPrinterParser implements DateTimePrinterParser { + /** Cache of formatters. */ + private static final ConcurrentMap FORMATTER_CACHE = new ConcurrentHashMap<>(16, 0.75f, 2); + private final FormatStyle dateStyle; private final FormatStyle timeStyle; @@ -3578,6 +4004,9 @@ /** * Gets the formatter to use. + *

    + * The formatter will be the most appropriate to use for the date and time style in the locale. + * For example, some locales will use the month name while others will use the number. * * @param locale the locale to use, not null * @param chrono the chronology to use, not null @@ -3585,8 +4014,32 @@ * @throws IllegalArgumentException if the formatter cannot be found */ private DateTimeFormatter formatter(Locale locale, Chronology chrono) { - return DateTimeFormatStyleProvider.getInstance() - .getFormatter(dateStyle, timeStyle, chrono, locale); + String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle; + DateTimeFormatter formatter = FORMATTER_CACHE.get(key); + if (formatter == null) { + LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale); + String pattern = lr.getJavaTimeDateTimePattern( + convertStyle(timeStyle), convertStyle(dateStyle), chrono.getCalendarType()); + formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale); + DateTimeFormatter old = FORMATTER_CACHE.putIfAbsent(key, formatter); + if (old != null) { + formatter = old; + } + } + return formatter; + } + + /** + * Converts the given FormatStyle to the java.text.DateFormat style. + * + * @param style the FormatStyle style + * @return the int style, or -1 if style is null, indicating unrequired + */ + private int convertStyle(FormatStyle style) { + if (style == null) { + return -1; + } + return style.ordinal(); // indices happen to align } @Override @@ -3596,7 +4049,6 @@ } } - //----------------------------------------------------------------------- /** * Prints or parses a localized pattern from a localized field. @@ -3641,14 +4093,23 @@ WeekFields weekDef = WeekFields.of(locale); TemporalField field = null; switch (chr) { + case 'Y': + field = weekDef.weekBasedYear(); + if (count == 2) { + return new ReducedPrinterParser(field, 2, 2000); + } else { + return new NumberPrinterParser(field, count, 19, + (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1); + } case 'e': + case 'c': field = weekDef.dayOfWeek(); break; case 'w': - field = weekDef.weekOfMonth(); + field = weekDef.weekOfWeekBasedYear(); break; case 'W': - field = weekDef.weekOfYear(); + field = weekDef.weekOfMonth(); break; default: throw new IllegalStateException("unreachable"); @@ -3658,11 +4119,41 @@ @Override public String toString() { - return String.format("WeekBased(%c%d)", chr, count); + StringBuilder sb = new StringBuilder(30); + sb.append("Localized("); + if (chr == 'Y') { + if (count == 1) { + sb.append("WeekBasedYear"); + } else if (count == 2) { + sb.append("ReducedValue(WeekBasedYear,2,2000)"); + } else { + sb.append("WeekBasedYear,").append(count).append(",") + .append(19).append(",") + .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD); + } + } else { + switch (chr) { + case 'c': + case 'e': + sb.append("DayOfWeek"); + break; + case 'w': + sb.append("WeekOfWeekBasedYear"); + break; + case 'W': + sb.append("WeekOfMonth"); + break; + default: + break; + } + sb.append(","); + sb.append(count); + } + sb.append(")"); + return sb.toString(); } } - //------------------------------------------------------------------------- /** * Length comparator. @@ -3673,5 +4164,4 @@ return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length(); } }; - } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/DateTimeParseContext.java --- a/src/share/classes/java/time/format/DateTimeParseContext.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/format/DateTimeParseContext.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,19 +61,12 @@ */ package java.time.format; -import java.time.DateTimeException; import java.time.ZoneId; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; -import java.time.temporal.ChronoField; -import java.time.temporal.Queries; -import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; -import java.time.temporal.TemporalQuery; import java.util.ArrayList; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; import java.util.Objects; /** @@ -83,8 +76,8 @@ * It has the ability to store and retrieve the parsed values and manage optional segments. * It also provides key information to the parsing methods. *

    - * Once parsing is complete, the {@link #toBuilder()} is typically used - * to obtain a builder that can combine the separate parsed fields into meaningful values. + * Once parsing is complete, the {@link #toParsed()} is used to obtain the data. + * It contains a method to resolve the separate parsed fields into meaningful values. * *

    Specification for implementors

    * This class is a mutable context intended for use from a single thread. @@ -93,7 +86,7 @@ * * @since 1.8 */ -final class DateTimeParseContext implements TemporalAccessor { +final class DateTimeParseContext { /** * The formatter, not null. @@ -306,6 +299,17 @@ return parsed.get(parsed.size() - 1); } + /** + * Gets the result of the parse. + * + * @return the result of the parse, not null + */ + Parsed toParsed() { + Parsed parsed = currentParsed(); + parsed.effectiveChrono = getEffectiveChronology(); + return parsed; + } + //----------------------------------------------------------------------- /** * Gets the first value that was parsed for the specified field. @@ -368,111 +372,6 @@ //----------------------------------------------------------------------- /** - * Returns a {@code DateTimeBuilder} that can be used to interpret - * the results of the parse. - *

    - * This method is typically used once parsing is complete to obtain the parsed data. - * Parsing will typically result in separate fields, such as year, month and day. - * The returned builder can be used to combine the parsed data into meaningful - * objects such as {@code LocalDate}, potentially applying complex processing - * to handle invalid parsed data. - * - * @return a new builder with the results of the parse, not null - */ - DateTimeBuilder toBuilder() { - Parsed parsed = currentParsed(); - DateTimeBuilder builder = new DateTimeBuilder(); - for (Map.Entry fv : parsed.fieldValues.entrySet()) { - builder.addFieldValue(fv.getKey(), fv.getValue()); - } - builder.addObject(getEffectiveChronology()); - if (parsed.zone != null) { - builder.addObject(parsed.zone); - } - return builder; - } - - /** - * Resolves the fields in this context. - * - * @return this, for method chaining - * @throws DateTimeException if resolving one field results in a value for - * another field that is in conflict - */ - DateTimeParseContext resolveFields() { - Parsed data = currentParsed(); - outer: - while (true) { - for (Map.Entry entry : data.fieldValues.entrySet()) { - TemporalField targetField = entry.getKey(); - Map changes = targetField.resolve(this, entry.getValue()); - if (changes != null) { - resolveMakeChanges(data, targetField, changes); - data.fieldValues.remove(targetField); // helps avoid infinite loops - continue outer; // have to restart to avoid concurrent modification - } - } - break; - } - return this; - } - - private void resolveMakeChanges(Parsed data, TemporalField targetField, Map changes) { - for (Map.Entry change : changes.entrySet()) { - TemporalField changeField = change.getKey(); - Long changeValue = change.getValue(); - Objects.requireNonNull(changeField, "changeField"); - if (changeValue != null) { - Long old = currentParsed().fieldValues.put(changeField, changeValue); - if (old != null && old.longValue() != changeValue.longValue()) { - throw new DateTimeException("Conflict found: " + changeField + " " + old + - " differs from " + changeField + " " + changeValue + - " while resolving " + targetField); - } - } else { - data.fieldValues.remove(changeField); - } - } - } - - //----------------------------------------------------------------------- - // TemporalAccessor methods - // should only to be used once parsing is complete - @Override - public boolean isSupported(TemporalField field) { - if (currentParsed().fieldValues.containsKey(field)) { - return true; - } - return (field instanceof ChronoField == false) && field.isSupportedBy(this); - } - - @Override - public long getLong(TemporalField field) { - Long value = currentParsed().fieldValues.get(field); - if (value != null) { - return value; - } - if (field instanceof ChronoField) { - throw new DateTimeException("Unsupported field: " + field.getName()); - } - return field.getFrom(this); - } - - @SuppressWarnings("unchecked") - @Override - public R query(TemporalQuery query) { - if (query == Queries.chronology()) { - return (R) currentParsed().chrono; - } else if (query == Queries.zoneId()) { - return (R) currentParsed().zone; - } else if (query == Queries.precision()) { - return null; - } - return query.queryFrom(this); - } - - //----------------------------------------------------------------------- - /** * Returns a string version of the context for debugging. * * @return a string representation of the context data, not null @@ -482,27 +381,4 @@ return currentParsed().toString(); } - //----------------------------------------------------------------------- - /** - * Temporary store of parsed data. - */ - private static final class Parsed { - Chronology chrono = null; - ZoneId zone = null; - final Map fieldValues = new HashMap<>(); - private Parsed() { - } - protected Parsed copy() { - Parsed cloned = new Parsed(); - cloned.chrono = this.chrono; - cloned.zone = this.zone; - cloned.fieldValues.putAll(this.fieldValues); - return cloned; - } - @Override - public String toString() { - return fieldValues.toString() + "," + chrono + "," + zone; - } - } - } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/DateTimePrintContext.java --- a/src/share/classes/java/time/format/DateTimePrintContext.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/format/DateTimePrintContext.java Sat Apr 13 21:51:36 2013 +0100 @@ -63,14 +63,16 @@ import static java.time.temporal.ChronoField.EPOCH_DAY; import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; import java.time.DateTimeException; import java.time.Instant; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.chrono.ChronoLocalDate; import java.time.chrono.Chronology; +import java.time.chrono.IsoChronology; import java.time.temporal.ChronoField; -import java.time.chrono.ChronoLocalDate; -import java.time.temporal.Queries; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; @@ -118,20 +120,20 @@ } private static TemporalAccessor adjust(final TemporalAccessor temporal, DateTimeFormatter formatter) { - // normal case first + // normal case first (early return is an optimization) Chronology overrideChrono = formatter.getChronology(); ZoneId overrideZone = formatter.getZone(); if (overrideChrono == null && overrideZone == null) { return temporal; } - // ensure minimal change - Chronology temporalChrono = Chronology.from(temporal); // default to ISO, handles Instant - ZoneId temporalZone = temporal.query(Queries.zone()); // zone then offset, handles OffsetDateTime - if (temporal.isSupported(EPOCH_DAY) == false || Objects.equals(overrideChrono, temporalChrono)) { + // ensure minimal change (early return is an optimization) + Chronology temporalChrono = temporal.query(TemporalQuery.chronology()); + ZoneId temporalZone = temporal.query(TemporalQuery.zoneId()); + if (Objects.equals(overrideChrono, temporalChrono)) { overrideChrono = null; } - if (temporal.isSupported(INSTANT_SECONDS) == false || Objects.equals(overrideZone, temporalZone)) { + if (Objects.equals(overrideZone, temporalZone)) { overrideZone = null; } if (overrideChrono == null && overrideZone == null) { @@ -139,53 +141,83 @@ } // make adjustment - if (overrideChrono != null && overrideZone != null) { - return overrideChrono.zonedDateTime(Instant.from(temporal), overrideZone); - } else if (overrideZone != null) { - return temporalChrono.zonedDateTime(Instant.from(temporal), overrideZone); - } else { // overrideChrono != null - // need class here to handle non-standard cases - final ChronoLocalDate date = overrideChrono.date(temporal); - return new TemporalAccessor() { - @Override - public boolean isSupported(TemporalField field) { - return temporal.isSupported(field); - } - @Override - public ValueRange range(TemporalField field) { - if (field instanceof ChronoField) { - if (((ChronoField) field).isDateField()) { - return date.range(field); - } else { - return temporal.range(field); + final Chronology effectiveChrono = (overrideChrono != null ? overrideChrono : temporalChrono); + if (overrideZone != null) { + // if have zone and instant, calculation is simple, defaulting chrono if necessary + if (temporal.isSupported(INSTANT_SECONDS)) { + Chronology chrono = (effectiveChrono != null ? effectiveChrono : IsoChronology.INSTANCE); + return chrono.zonedDateTime(Instant.from(temporal), overrideZone); + } + // block changing zone on OffsetTime, and similar problem cases + if (overrideZone.normalized() instanceof ZoneOffset && temporal.isSupported(OFFSET_SECONDS) && + temporal.get(OFFSET_SECONDS) != overrideZone.getRules().getOffset(Instant.EPOCH).getTotalSeconds()) { + throw new DateTimeException("Unable to apply override zone '" + overrideZone + + "' because the temporal object being formatted has a different offset but" + + " does not represent an instant: " + temporal); + } + } + final ZoneId effectiveZone = (overrideZone != null ? overrideZone : temporalZone); + final ChronoLocalDate effectiveDate; + if (overrideChrono != null) { + if (temporal.isSupported(EPOCH_DAY)) { + effectiveDate = effectiveChrono.date(temporal); + } else { + // check for date fields other than epoch-day, ignoring case of converting null to ISO + if (!(overrideChrono == IsoChronology.INSTANCE && temporalChrono == null)) { + for (ChronoField f : ChronoField.values()) { + if (f.isDateBased() && temporal.isSupported(f)) { + throw new DateTimeException("Unable to apply override chronology '" + overrideChrono + + "' because the temporal object being formatted contains date fields but" + + " does not represent a whole date: " + temporal); } } - return field.rangeRefinedBy(this); } - @Override - public long getLong(TemporalField field) { - if (field instanceof ChronoField) { - if (((ChronoField) field).isDateField()) { - return date.getLong(field); - } else { - return temporal.getLong(field); - } - } - return field.getFrom(this); + effectiveDate = null; + } + } else { + effectiveDate = null; + } + + // combine available data + // this is a non-standard temporal that is almost a pure delegate + // this better handles map-like underlying temporal instances + return new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + if (effectiveDate != null && field.isDateBased()) { + return effectiveDate.isSupported(field); + } + return temporal.isSupported(field); + } + @Override + public ValueRange range(TemporalField field) { + if (effectiveDate != null && field.isDateBased()) { + return effectiveDate.range(field); } - @SuppressWarnings("unchecked") - @Override - public R query(TemporalQuery query) { - if (query == Queries.chronology()) { - return (R) date.getChronology(); - } - if (query == Queries.zoneId() || query == Queries.precision()) { - return temporal.query(query); - } - return query.queryFrom(this); + return temporal.range(field); + } + @Override + public long getLong(TemporalField field) { + if (effectiveDate != null && field.isDateBased()) { + return effectiveDate.getLong(field); } - }; - } + return temporal.getLong(field); + } + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == TemporalQuery.chronology()) { + return (R) effectiveChrono; + } + if (query == TemporalQuery.zoneId()) { + return (R) effectiveZone; + } + if (query == TemporalQuery.precision()) { + return temporal.query(query); + } + return query.queryFrom(this); + } + }; } //----------------------------------------------------------------------- diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/DateTimeTextProvider.java --- a/src/share/classes/java/time/format/DateTimeTextProvider.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/format/DateTimeTextProvider.java Sat Apr 13 21:51:36 2013 +0100 @@ -70,6 +70,7 @@ import java.time.chrono.IsoChronology; import java.time.chrono.JapaneseChronology; import java.time.temporal.ChronoField; +import java.time.temporal.IsoFields; import java.time.temporal.TemporalField; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; @@ -82,10 +83,13 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.ResourceBundle; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import sun.util.locale.provider.CalendarDataUtility; +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.LocaleResources; /** * A provider to obtain the textual form of a date-time field. @@ -141,15 +145,6 @@ return null; } - private static int toStyle(TextStyle style) { - if (style == TextStyle.FULL) { - return Calendar.LONG_FORMAT; - } else if (style == TextStyle.SHORT) { - return Calendar.SHORT_FORMAT; - } - return Calendar.NARROW_STANDALONE; - } - /** * Gets the text for the specified chrono, field, locale and style * for the purpose of formatting. @@ -158,7 +153,7 @@ * The null return value should be used if there is no applicable text, or * if the text would be a numeric representation of the value. * - * @param chrono the Chronology to get text for, not null + * @param chrono the Chronology to get text for, not null * @param field the field to get text for, not null * @param value the field value to get text for, not null * @param style the style to get text for, not null @@ -200,8 +195,8 @@ } else { return null; } - return CalendarDataUtility.retrieveCldrFieldValueName( - chrono.getCalendarType(), fieldIndex, fieldValue, toStyle(style), locale); + return CalendarDataUtility.retrieveJavaTimeFieldValueName( + chrono.getCalendarType(), fieldIndex, fieldValue, style.toCalendarStyle(), locale); } /** @@ -238,7 +233,7 @@ * if the text would be a numeric representation of the value. * Text can only be parsed if all the values for that field-style-locale combination are unique. * - * @param chrono the Chronology to get text for, not null + * @param chrono the Chronology to get text for, not null * @param field the field to get text for, not null * @param style the style to get text for, null for all parsable text * @param locale the locale to get text for, not null @@ -270,17 +265,17 @@ return null; } - Map map = CalendarDataUtility.retrieveCldrFieldValueNames( - chrono.getCalendarType(), fieldIndex, toStyle(style), locale); + int calendarStyle = (style == null) ? Calendar.ALL_STYLES : style.toCalendarStyle(); + Map map = CalendarDataUtility.retrieveJavaTimeFieldValueNames( + chrono.getCalendarType(), fieldIndex, calendarStyle, locale); if (map == null) { return null; } - List> list = new ArrayList<>(map.size()); switch (fieldIndex) { case Calendar.ERA: - for (String key : map.keySet()) { - int era = map.get(key); + for (Map.Entry entry : map.entrySet()) { + int era = entry.getValue(); if (chrono == JapaneseChronology.INSTANCE) { if (era == 0) { era = -999; @@ -288,22 +283,22 @@ era -= 2; } } - list.add(createEntry(key, (long) era)); + list.add(createEntry(entry.getKey(), (long)era)); } break; case Calendar.MONTH: - for (String key : map.keySet()) { - list.add(createEntry(key, (long)(map.get(key) + 1))); + for (Map.Entry entry : map.entrySet()) { + list.add(createEntry(entry.getKey(), (long)(entry.getValue() + 1))); } break; case Calendar.DAY_OF_WEEK: - for (String key : map.keySet()) { - list.add(createEntry(key, (long)toWeekDay(map.get(key)))); + for (Map.Entry entry : map.entrySet()) { + list.add(createEntry(entry.getKey(), (long)toWeekDay(entry.getValue()))); } break; default: - for (String key : map.keySet()) { - list.add(createEntry(key, (long)map.get(key))); + for (Map.Entry entry : map.entrySet()) { + list.add(createEntry(entry.getKey(), (long)entry.getValue())); } break; } @@ -333,11 +328,47 @@ Map> styleMap = new HashMap<>(); if (field == ERA) { for (TextStyle textStyle : TextStyle.values()) { + if (textStyle.isStandalone()) { + // Stand-alone isn't applicable to era names. + continue; + } + Map displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( + "gregory", Calendar.ERA, textStyle.toCalendarStyle(), locale); + if (displayNames != null) { + Map map = new HashMap<>(); + for (Entry entry : displayNames.entrySet()) { + map.put((long) entry.getValue(), entry.getKey()); + } + if (!map.isEmpty()) { + styleMap.put(textStyle, map); + } + } + } + return new LocaleStore(styleMap); + } + + if (field == MONTH_OF_YEAR) { + for (TextStyle textStyle : TextStyle.values()) { + Map displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( + "gregory", Calendar.MONTH, textStyle.toCalendarStyle(), locale); Map map = new HashMap<>(); - for (Entry entry : - CalendarDataUtility.retrieveCldrFieldValueNames( - "gregory", Calendar.ERA, toStyle(textStyle), locale).entrySet()) { - map.put((long) entry.getValue(), entry.getKey()); + if (displayNames != null) { + for (Entry entry : displayNames.entrySet()) { + map.put((long) (entry.getValue() + 1), entry.getKey()); + } + + } else { + // Narrow names may have duplicated names, such as "J" for January, Jun, July. + // Get names one by one in that case. + for (int month = Calendar.JANUARY; month <= Calendar.DECEMBER; month++) { + String name; + name = CalendarDataUtility.retrieveJavaTimeFieldValueName( + "gregory", Calendar.MONTH, month, textStyle.toCalendarStyle(), locale); + if (name == null) { + break; + } + map.put((long) (month + 1), name); + } } if (!map.isEmpty()) { styleMap.put(textStyle, map); @@ -346,72 +377,77 @@ return new LocaleStore(styleMap); } - if (field == MONTH_OF_YEAR) { - Map map = new HashMap<>(); - for (Entry entry : - CalendarDataUtility.retrieveCldrFieldValueNames( - "gregory", Calendar.MONTH, Calendar.LONG_FORMAT, locale).entrySet()) { - map.put((long) (entry.getValue() + 1), entry.getKey()); - } - styleMap.put(TextStyle.FULL, map); + if (field == DAY_OF_WEEK) { + for (TextStyle textStyle : TextStyle.values()) { + Map displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( + "gregory", Calendar.DAY_OF_WEEK, textStyle.toCalendarStyle(), locale); + Map map = new HashMap<>(); + if (displayNames != null) { + for (Entry entry : displayNames.entrySet()) { + map.put((long)toWeekDay(entry.getValue()), entry.getKey()); + } - map = new HashMap<>(); - for (Entry entry : - CalendarDataUtility.retrieveCldrFieldValueNames( - "gregory", Calendar.MONTH, Calendar.SHORT_FORMAT, locale).entrySet()) { - map.put((long) (entry.getValue() + 1), entry.getKey()); - } - styleMap.put(TextStyle.SHORT, map); - - map = new HashMap<>(); - for (int month = Calendar.JANUARY; month <= Calendar.DECEMBER; month++) { - String name; - name = CalendarDataUtility.retrieveCldrFieldValueName( - "gregory", Calendar.MONTH, month, Calendar.NARROW_STANDALONE, locale); - if (name != null) { - map.put((long)(month + 1), name); + } else { + // Narrow names may have duplicated names, such as "S" for Sunday and Saturday. + // Get names one by one in that case. + for (int wday = Calendar.SUNDAY; wday <= Calendar.SATURDAY; wday++) { + String name; + name = CalendarDataUtility.retrieveJavaTimeFieldValueName( + "gregory", Calendar.DAY_OF_WEEK, wday, textStyle.toCalendarStyle(), locale); + if (name == null) { + break; + } + map.put((long)toWeekDay(wday), name); + } } - } - if (!map.isEmpty()) { - styleMap.put(TextStyle.NARROW, map); + if (!map.isEmpty()) { + styleMap.put(textStyle, map); + } } return new LocaleStore(styleMap); } - if (field == DAY_OF_WEEK) { - Map map = new HashMap<>(); - for (Entry entry : - CalendarDataUtility.retrieveCldrFieldValueNames( - "gregory", Calendar.DAY_OF_WEEK, Calendar.LONG_FORMAT, locale).entrySet()) { - map.put((long)toWeekDay(entry.getValue()), entry.getKey()); + if (field == AMPM_OF_DAY) { + for (TextStyle textStyle : TextStyle.values()) { + if (textStyle.isStandalone()) { + // Stand-alone isn't applicable to AM/PM. + continue; + } + Map displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( + "gregory", Calendar.AM_PM, textStyle.toCalendarStyle(), locale); + if (displayNames != null) { + Map map = new HashMap<>(); + for (Entry entry : displayNames.entrySet()) { + map.put((long) entry.getValue(), entry.getKey()); + } + if (!map.isEmpty()) { + styleMap.put(textStyle, map); + } + } } - styleMap.put(TextStyle.FULL, map); - map = new HashMap<>(); - for (Entry entry : - CalendarDataUtility.retrieveCldrFieldValueNames( - "gregory", Calendar.DAY_OF_WEEK, Calendar.SHORT_FORMAT, locale).entrySet()) { - map.put((long) toWeekDay(entry.getValue()), entry.getKey()); - } - styleMap.put(TextStyle.SHORT, map); - map = new HashMap<>(); - for (int wday = Calendar.SUNDAY; wday <= Calendar.SATURDAY; wday++) { - map.put((long) toWeekDay(wday), - CalendarDataUtility.retrieveCldrFieldValueName( - "gregory", Calendar.DAY_OF_WEEK, wday, Calendar.NARROW_FORMAT, locale)); - } - styleMap.put(TextStyle.NARROW, map); return new LocaleStore(styleMap); } - if (field == AMPM_OF_DAY) { - Map map = new HashMap<>(); - for (Entry entry : - CalendarDataUtility.retrieveCldrFieldValueNames( - "gregory", Calendar.AM_PM, Calendar.LONG_FORMAT, locale).entrySet()) { - map.put((long) entry.getValue(), entry.getKey()); + if (field == IsoFields.QUARTER_OF_YEAR) { + // The order of keys must correspond to the TextStyle.values() order. + final String[] keys = { + "QuarterNames", + "standalone.QuarterNames", + "QuarterAbbreviations", + "standalone.QuarterAbbreviations", + "QuarterNarrows", + "standalone.QuarterNarrows", + }; + for (int i = 0; i < keys.length; i++) { + String[] names = getLocalizedResource(keys[i], locale); + if (names != null) { + Map map = new HashMap<>(); + for (int q = 0; q < names.length; q++) { + map.put((long) (q + 1), names[q]); + } + styleMap.put(TextStyle.values()[i], map); + } } - styleMap.put(TextStyle.FULL, map); - styleMap.put(TextStyle.SHORT, map); // re-use, as we don't have different data return new LocaleStore(styleMap); } @@ -430,6 +466,23 @@ } /** + * Returns the localized resource of the given key and locale, or null + * if no localized resource is available. + * + * @param key the key of the localized resource, not null + * @param locale the locale, not null + * @return the localized resource, or null if not available + * @throws NullPointerException if key or locale is null + */ + @SuppressWarnings("unchecked") + static T getLocalizedResource(String key, Locale locale) { + LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() + .getLocaleResources(locale); + ResourceBundle rb = lr.getJavaTimeFormatData(); + return rb.containsKey(key) ? (T) rb.getObject(key) : null; + } + + /** * Stores the text for a single locale. *

    * Some fields have a textual representation, such as day-of-week or month-of-year. @@ -457,9 +510,9 @@ this.valueTextMap = valueTextMap; Map>> map = new HashMap<>(); List> allList = new ArrayList<>(); - for (TextStyle style : valueTextMap.keySet()) { + for (Map.Entry> vtmEntry : valueTextMap.entrySet()) { Map> reverse = new HashMap<>(); - for (Map.Entry entry : valueTextMap.get(style).entrySet()) { + for (Map.Entry entry : vtmEntry.getValue().entrySet()) { if (reverse.put(entry.getValue(), createEntry(entry.getValue(), entry.getKey())) != null) { // TODO: BUG: this has no effect continue; // not parsable, try next style @@ -467,7 +520,7 @@ } List> list = new ArrayList<>(reverse.values()); Collections.sort(list, COMPARATOR); - map.put(style, list); + map.put(vtmEntry.getKey(), list); allList.addAll(list); map.put(null, allList); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/Parsed.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/time/format/Parsed.java Sat Apr 13 21:51:36 2013 +0100 @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Copyright (c) 2008-2013, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package java.time.format; + +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; +import static java.time.temporal.ChronoField.HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.chrono.ChronoLocalDate; +import java.time.chrono.Chronology; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +/** + * A store of parsed data. + *

    + * This class is used during parsing to collect the data. Part of the parsing process + * involves handling optional blocks and multiple copies of the data get created to + * support the necessary backtracking. + *

    + * Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}. + * In most cases, it is only exposed once the fields have been resolved. + * + *

    Specification for implementors

    + * This class is a mutable context intended for use from a single thread. + * Usage of the class is thread-safe within standard parsing as a new instance of this class + * is automatically created for each parse and parsing is single-threaded + * + * @since 1.8 + */ +final class Parsed implements TemporalAccessor { + // some fields are accessed using package scope from DateTimeParseContext + + /** + * The parsed fields. + */ + final Map fieldValues = new HashMap<>(); + /** + * The parsed zone. + */ + ZoneId zone; + /** + * The parsed chronology. + */ + Chronology chrono; + /** + * The effective chronology. + */ + Chronology effectiveChrono; + /** + * The resolver style to use. + */ + private ResolverStyle resolverStyle; + /** + * The resolved date. + */ + private ChronoLocalDate date; + /** + * The resolved time. + */ + private LocalTime time; + + /** + * Creates an instance. + */ + Parsed() { + } + + /** + * Creates a copy. + */ + Parsed copy() { + // only copy fields used in parsing stage + Parsed cloned = new Parsed(); + cloned.fieldValues.putAll(this.fieldValues); + cloned.zone = this.zone; + cloned.chrono = this.chrono; + return cloned; + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (fieldValues.containsKey(field) || + (date != null && date.isSupported(field)) || + (time != null && time.isSupported(field))) { + return true; + } + return field != null && (field instanceof ChronoField == false) && field.isSupportedBy(this); + } + + @Override + public long getLong(TemporalField field) { + Objects.requireNonNull(field, "field"); + Long value = fieldValues.get(field); + if (value != null) { + return value; + } + if (date != null && date.isSupported(field)) { + return date.getLong(field); + } + if (time != null && time.isSupported(field)) { + return time.getLong(field); + } + if (field instanceof ChronoField) { + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); + } + return field.getFrom(this); + } + + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == TemporalQuery.zoneId()) { + return (R) zone; + } else if (query == TemporalQuery.chronology()) { + return (R) chrono; + } else if (query == TemporalQuery.localDate()) { + return (R) (date != null ? LocalDate.from(date) : null); + } else if (query == TemporalQuery.localTime()) { + return (R) time; + } else if (query == TemporalQuery.zone() || query == TemporalQuery.offset()) { + return query.queryFrom(this); + } else if (query == TemporalQuery.precision()) { + return null; // not a complete date/time + } + // inline TemporalAccessor.super.query(query) as an optimization + // non-JDK classes are not permitted to make this optimization + return query.queryFrom(this); + } + + //----------------------------------------------------------------------- + /** + * Resolves the fields in this context. + * + * @param resolverStyle the resolver style, not null + * @param resolverFields the fields to use for resolving, null for all fields + * @return this, for method chaining + * @throws DateTimeException if resolving one field results in a value for + * another field that is in conflict + */ + TemporalAccessor resolve(ResolverStyle resolverStyle, Set resolverFields) { + if (resolverFields != null) { + fieldValues.keySet().retainAll(resolverFields); + } + this.resolverStyle = resolverStyle; + chrono = effectiveChrono; + resolveFields(); + resolveTimeLenient(); + crossCheck(); + return this; + } + + //----------------------------------------------------------------------- + private void resolveFields() { + // resolve ChronoField + resolveDateFields(); + resolveTimeFields(); + + // if any other fields, handle them + // any lenient date resolution should return epoch-day + if (fieldValues.size() > 0) { + boolean changed = false; + outer: + while (true) { + for (Map.Entry entry : fieldValues.entrySet()) { + TemporalField targetField = entry.getKey(); + Map changes = targetField.resolve(this, entry.getValue(), resolverStyle); + if (changes != null) { + changed = true; + resolveFieldsMakeChanges(targetField, changes); + fieldValues.remove(targetField); // helps avoid infinite loops + continue outer; // have to restart to avoid concurrent modification + } + } + break; + } + // if something changed then have to redo ChronoField resolve + if (changed) { + resolveDateFields(); + resolveTimeFields(); + } + } + } + + private void resolveFieldsMakeChanges(TemporalField targetField, Map changes) { + for (Map.Entry change : changes.entrySet()) { + TemporalField changeField = change.getKey(); + Long changeValue = change.getValue(); + Objects.requireNonNull(changeField, "changeField"); + if (changeValue != null) { + updateCheckConflict(targetField, changeField, changeValue); + } else { + fieldValues.remove(changeField); + } + } + } + + private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) { + Long old = fieldValues.put(changeField, changeValue); + if (old != null && old.longValue() != changeValue.longValue()) { + throw new DateTimeException("Conflict found: " + changeField + " " + old + + " differs from " + changeField + " " + changeValue + + " while resolving " + targetField); + } + } + + //----------------------------------------------------------------------- + private void resolveDateFields() { + updateCheckConflict(chrono.resolveDate(fieldValues, resolverStyle)); + } + + private void updateCheckConflict(ChronoLocalDate cld) { + if (date != null) { + if (cld != null && date.equals(cld) == false) { + throw new DateTimeException("Conflict found: Fields resolved to two different dates: " + date + " " + cld); + } + } else { + date = cld; + } + } + + //----------------------------------------------------------------------- + private void resolveTimeFields() { + // simplify fields + if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) { + long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY); + updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch); + } + if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) { + long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM); + updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch); + } + if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) { + long ap = fieldValues.remove(AMPM_OF_DAY); + long hap = fieldValues.remove(HOUR_OF_AMPM); + updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap); + } + if (fieldValues.containsKey(MICRO_OF_DAY)) { + long cod = fieldValues.remove(MICRO_OF_DAY); + updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1_000_000L); + updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1_000_000L); + } + if (fieldValues.containsKey(MILLI_OF_DAY)) { + long lod = fieldValues.remove(MILLI_OF_DAY); + updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1_000); + updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1_000); + } + if (fieldValues.containsKey(SECOND_OF_DAY)) { + long sod = fieldValues.remove(SECOND_OF_DAY); + updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600); + updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60); + updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60); + } + if (fieldValues.containsKey(MINUTE_OF_DAY)) { + long mod = fieldValues.remove(MINUTE_OF_DAY); + updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60); + updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60); + } + + // combine partial second fields strictly, leaving lenient expansion to later + if (fieldValues.containsKey(NANO_OF_SECOND)) { + long nos = fieldValues.get(NANO_OF_SECOND); + if (fieldValues.containsKey(MICRO_OF_SECOND)) { + long cos = fieldValues.remove(MICRO_OF_SECOND); + nos = cos * 1000 + (nos % 1000); + updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, nos); + } + if (fieldValues.containsKey(MILLI_OF_SECOND)) { + long los = fieldValues.remove(MILLI_OF_SECOND); + updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, los * 1_000_000L + (nos % 1_000_000L)); + } + } + + // convert to time if possible + if (fieldValues.containsKey(NANO_OF_DAY)) { + long nod = fieldValues.remove(NANO_OF_DAY); + updateCheckConflict(LocalTime.ofNanoOfDay(nod)); + } + if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) && + fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) { + int hodVal = HOUR_OF_DAY.checkValidIntValue(fieldValues.remove(HOUR_OF_DAY)); + int mohVal = MINUTE_OF_HOUR.checkValidIntValue(fieldValues.remove(MINUTE_OF_HOUR)); + int somVal = SECOND_OF_MINUTE.checkValidIntValue(fieldValues.remove(SECOND_OF_MINUTE)); + int nosVal = NANO_OF_SECOND.checkValidIntValue(fieldValues.remove(NANO_OF_SECOND)); + updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal)); + } + } + + private void resolveTimeLenient() { + // leniently create a time from incomplete information + // done after everything else as it creates information from nothing + // which would break updateCheckConflict(field) + + if (time == null) { + // can only get here if NANO_OF_SECOND not present + if (fieldValues.containsKey(MILLI_OF_SECOND)) { + long los = fieldValues.remove(MILLI_OF_SECOND); + if (fieldValues.containsKey(MICRO_OF_SECOND)) { + // merge milli-of-second and micro-of-second for better error message + long cos = los * 1_000 + (fieldValues.get(MICRO_OF_SECOND) % 1_000); + updateCheckConflict(MILLI_OF_SECOND, MICRO_OF_SECOND, cos); + fieldValues.remove(MICRO_OF_SECOND); + fieldValues.put(NANO_OF_SECOND, cos * 1_000L); + } else { + // convert milli-of-second to nano-of-second + fieldValues.put(NANO_OF_SECOND, los * 1_000_000L); + } + } else if (fieldValues.containsKey(MICRO_OF_SECOND)) { + // convert micro-of-second to nano-of-second + long cos = fieldValues.remove(MICRO_OF_SECOND); + fieldValues.put(NANO_OF_SECOND, cos * 1_000L); + } + } + + // merge hour/minute/second/nano leniently + Long hod = fieldValues.get(HOUR_OF_DAY); + if (hod != null) { + int hodVal = HOUR_OF_DAY.checkValidIntValue(hod); + Long moh = fieldValues.get(MINUTE_OF_HOUR); + Long som = fieldValues.get(SECOND_OF_MINUTE); + Long nos = fieldValues.get(NANO_OF_SECOND); + + // check for invalid combinations that cannot be defaulted + if (time == null) { + if ((moh == null && (som != null || nos != null)) || + (moh != null && som == null && nos != null)) { + return; + } + } + + // default as necessary and build time + int mohVal = (moh != null ? MINUTE_OF_HOUR.checkValidIntValue(moh) : (time != null ? time.getMinute() : 0)); + int somVal = (som != null ? SECOND_OF_MINUTE.checkValidIntValue(som) : (time != null ? time.getSecond() : 0)); + int nosVal = (nos != null ? NANO_OF_SECOND.checkValidIntValue(nos) : (time != null ? time.getNano() : 0)); + updateCheckConflict(LocalTime.of(hodVal, mohVal, somVal, nosVal)); + fieldValues.remove(HOUR_OF_DAY); + fieldValues.remove(MINUTE_OF_HOUR); + fieldValues.remove(SECOND_OF_MINUTE); + fieldValues.remove(NANO_OF_SECOND); + } + } + + private void updateCheckConflict(LocalTime lt) { + if (time != null) { + if (lt != null && time.equals(lt) == false) { + throw new DateTimeException("Conflict found: Fields resolved to two different times: " + time + " " + lt); + } + } else { + time = lt; + } + } + + //----------------------------------------------------------------------- + private void crossCheck() { + // only cross-check date, time and date-time + // avoid object creation if possible + if (date != null) { + crossCheck(date); + } + if (time != null) { + crossCheck(time); + if (date != null && fieldValues.size() > 0) { + crossCheck(date.atTime(time)); + } + } + } + + private void crossCheck(TemporalAccessor target) { + for (Iterator> it = fieldValues.entrySet().iterator(); it.hasNext(); ) { + Entry entry = it.next(); + TemporalField field = entry.getKey(); + long val1; + try { + val1 = target.getLong(field); + } catch (RuntimeException ex) { + continue; + } + long val2 = entry.getValue(); + if (val1 != val2) { + throw new DateTimeException("Conflict found: Field " + field + " " + val1 + + " differs from " + field + " " + val2 + " derived from " + target); + } + it.remove(); + } + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + String str = fieldValues.toString() + "," + chrono + "," + zone; + if (date != null || time != null) { + str += " resolved to " + date + "," + time; + } + return str; + } + +} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/ResolverStyle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/time/format/ResolverStyle.java Sat Apr 13 21:51:36 2013 +0100 @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Copyright (c) 2008-2013, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package java.time.format; + +/** + * Enumeration of different ways to resolve dates and times. + *

    + * Parsing a text string occurs in two phases. + * Phase 1 is a basic text parse according to the fields added to the builder. + * Phase 2 resolves the parsed field-value pairs into date and/or time objects. + * This style is used to control how phase 2, resolving, happens. + * + *

    Specification for implementors

    + * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum ResolverStyle { + + /** + * Style to resolve dates and times strictly. + *

    + * Using strict resolution will ensure that all parsed values are within + * the outer range of valid values for the field. Individual fields may + * be further processed for strictness. + *

    + * For example, resolving year-month and day-of-month in the ISO calendar + * system using strict mode will ensure that the day-of-month is valid + * for the year-month, rejecting invalid values. + */ + STRICT, + /** + * Style to resolve dates and times in a smart, or intelligent, manner. + *

    + * Using smart resolution will perform the sensible default for each + * field, which may be the same as strict, the same as lenient, or a third + * behavior. Individual fields will interpret this differently. + *

    + * For example, resolving year-month and day-of-month in the ISO calendar + * system using smart mode will ensure that the day-of-month is valid + * for the year-month, rejecting invalid values, with the exception that + * February 29th in a year other than a leap year will be converted to + * February 28th. + */ + SMART, + /** + * Style to resolve dates and times leniently. + *

    + * Using lenient resolution will resolve the values in an appropriate + * lenient manner. Individual fields will interpret this differently. + *

    + * For example, lenient mode allows the month in the ISO calendar system + * to be outside the range 1 to 12. + */ + LENIENT; + +} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/format/TextStyle.java --- a/src/share/classes/java/time/format/TextStyle.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/format/TextStyle.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,33 +61,115 @@ */ package java.time.format; +import java.util.Calendar; + /** - * Enumeration of the style of text output to use. + * Enumeration of the style of text formatting and parsing. + *

    + * Text styles define three sizes for the formatted text - 'full', 'short' and 'narrow'. + * Each of these three sizes is available in both 'standard' and 'stand-alone' variations. *

    - * This defines the "size" of the text to be output. + * The difference between the three sizes is obvious in most languages. + * For example, in English the 'full' month is 'January', the 'short' month is 'Jan' + * and the 'narrow' month is 'J'. Note that the narrow size is often not unique. + * For example, 'January', 'June' and 'July' all have the 'narrow' text 'J'. + *

    + * The difference between the 'standard' and 'stand-alone' forms is trickier to describe + * as there is no difference in English. However, in other languages there is a difference + * in the word used when the text is used alone, as opposed to in a complete date. + * For example, the word used for a month when used alone in a date picker is different + * to the word used for month in association with a day and year in a date. * *

    Specification for implementors

    * This is immutable and thread-safe enum. - * - * @since 1.8 */ public enum TextStyle { // ordered from large to small + // ordered so that bit 0 of the ordinal indicates stand-alone. /** * Full text, typically the full description. * For example, day-of-week Monday might output "Monday". */ - FULL, + FULL(Calendar.LONG_FORMAT, 0), + /** + * Full text for stand-alone use, typically the full description. + * For example, day-of-week Monday might output "Monday". + */ + FULL_STANDALONE(Calendar.LONG_STANDALONE, 0), /** * Short text, typically an abbreviation. * For example, day-of-week Monday might output "Mon". */ - SHORT, + SHORT(Calendar.SHORT_FORMAT, 1), + /** + * Short text for stand-alone use, typically an abbreviation. + * For example, day-of-week Monday might output "Mon". + */ + SHORT_STANDALONE(Calendar.SHORT_STANDALONE, 1), /** * Narrow text, typically a single letter. * For example, day-of-week Monday might output "M". */ - NARROW; + NARROW(Calendar.NARROW_FORMAT, 1), + /** + * Narrow text for stand-alone use, typically a single letter. + * For example, day-of-week Monday might output "M". + */ + NARROW_STANDALONE(Calendar.NARROW_STANDALONE, 1); + + private final int calendarStyle; + private final int zoneNameStyleIndex; + + private TextStyle(int calendarStyle, int zoneNameStyleIndex) { + this.calendarStyle = calendarStyle; + this.zoneNameStyleIndex = zoneNameStyleIndex; + } + + /** + * Returns true if the Style is a stand-alone style. + * @return true if the style is a stand-alone style. + */ + public boolean isStandalone() { + return (ordinal() & 1) == 1; + } + + /** + * Returns the stand-alone style with the same size. + * @return the stand-alone style with the same size + */ + public TextStyle asStandalone() { + return TextStyle.values()[ordinal() | 1]; + } + /** + * Returns the normal style with the same size. + * + * @return the normal style with the same size + */ + public TextStyle asNormal() { + return TextStyle.values()[ordinal() & ~1]; + } + + /** + * Returns the {@code Calendar} style corresponding to this {@code TextStyle}. + * + * @return the corresponding {@code Calendar} style + */ + int toCalendarStyle() { + return calendarStyle; + } + + /** + * Returns the relative index value to an element of the {@link + * java.text.DateFormatSymbols#getZoneStrings() DateFormatSymbols.getZoneStrings()} + * value, 0 for long names and 1 for short names (abbreviations). Note that these values + * do not correspond to the {@link java.util.TimeZone#LONG} and {@link + * java.util.TimeZone#SHORT} values. + * + * @return the relative index value to time zone names array + */ + int zoneNameStyleIndex() { + return zoneNameStyleIndex; + } } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/Adjusters.java --- a/src/share/classes/java/time/temporal/Adjusters.java Sat Apr 13 20:16:00 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,502 +0,0 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * This file is available under and governed by the GNU General Public - * License version 2 only, as published by the Free Software Foundation. - * However, the following notice accompanied the original version of this - * file: - * - * Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of JSR-310 nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package java.time.temporal; - -import static java.time.temporal.ChronoField.DAY_OF_MONTH; -import static java.time.temporal.ChronoField.DAY_OF_WEEK; -import static java.time.temporal.ChronoField.DAY_OF_YEAR; -import static java.time.temporal.ChronoUnit.DAYS; -import static java.time.temporal.ChronoUnit.MONTHS; -import static java.time.temporal.ChronoUnit.YEARS; - -import java.time.DayOfWeek; -import java.util.Objects; - -/** - * Common implementations of {@code TemporalAdjuster}. - *

    - * This class provides common implementations of {@link TemporalAdjuster}. - * They are especially useful to document the intent of business logic and - * often link well to requirements. - * For example, these two pieces of code do the same thing, but the second - * one is clearer (assuming that there is a static import of this class): - *

    - *  // direct manipulation
    - *  date.withDayOfMonth(1).plusMonths(1).minusDays(1);
    - *  // use of an adjuster from this class
    - *  date.with(lastDayOfMonth());
    - * 
    - * There are two equivalent ways of using a {@code TemporalAdjuster}. - * The first is to invoke the method on the interface directly. - * The second is to use {@link Temporal#with(TemporalAdjuster)}: - *
    - *   // these two lines are equivalent, but the second approach is recommended
    - *   dateTime = adjuster.adjustInto(dateTime);
    - *   dateTime = dateTime.with(adjuster);
    - * 
    - * It is recommended to use the second approach, {@code with(TemporalAdjuster)}, - * as it is a lot clearer to read in code. - * - *

    Specification for implementors

    - * This is a thread-safe utility class. - * All returned adjusters are immutable and thread-safe. - * - * @since 1.8 - */ -public final class Adjusters { - - /** - * Private constructor since this is a utility class. - */ - private Adjusters() { - } - - //----------------------------------------------------------------------- - /** - * Returns the "first day of month" adjuster, which returns a new date set to - * the first day of the current month. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 will return 2011-01-01.
    - * The input 2011-02-15 will return 2011-02-01. - *

    - * The behavior is suitable for use with most calendar systems. - * It is equivalent to: - *

    -     *  temporal.with(DAY_OF_MONTH, 1);
    -     * 
    - * - * @return the first day-of-month adjuster, not null - */ - public static TemporalAdjuster firstDayOfMonth() { - return Impl.FIRST_DAY_OF_MONTH; - } - - /** - * Returns the "last day of month" adjuster, which returns a new date set to - * the last day of the current month. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 will return 2011-01-31.
    - * The input 2011-02-15 will return 2011-02-28.
    - * The input 2012-02-15 will return 2012-02-29 (leap year).
    - * The input 2011-04-15 will return 2011-04-30. - *

    - * The behavior is suitable for use with most calendar systems. - * It is equivalent to: - *

    -     *  long lastDay = temporal.range(DAY_OF_MONTH).getMaximum();
    -     *  temporal.with(DAY_OF_MONTH, lastDay);
    -     * 
    - * - * @return the last day-of-month adjuster, not null - */ - public static TemporalAdjuster lastDayOfMonth() { - return Impl.LAST_DAY_OF_MONTH; - } - - /** - * Returns the "first day of next month" adjuster, which returns a new date set to - * the first day of the next month. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 will return 2011-02-01.
    - * The input 2011-02-15 will return 2011-03-01. - *

    - * The behavior is suitable for use with most calendar systems. - * It is equivalent to: - *

    -     *  temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS);
    -     * 
    - * - * @return the first day of next month adjuster, not null - */ - public static TemporalAdjuster firstDayOfNextMonth() { - return Impl.FIRST_DAY_OF_NEXT_MONTH; - } - - //----------------------------------------------------------------------- - /** - * Returns the "first day of year" adjuster, which returns a new date set to - * the first day of the current year. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 will return 2011-01-01.
    - * The input 2011-02-15 will return 2011-01-01.
    - *

    - * The behavior is suitable for use with most calendar systems. - * It is equivalent to: - *

    -     *  temporal.with(DAY_OF_YEAR, 1);
    -     * 
    - * - * @return the first day-of-year adjuster, not null - */ - public static TemporalAdjuster firstDayOfYear() { - return Impl.FIRST_DAY_OF_YEAR; - } - - /** - * Returns the "last day of year" adjuster, which returns a new date set to - * the last day of the current year. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 will return 2011-12-31.
    - * The input 2011-02-15 will return 2011-12-31.
    - *

    - * The behavior is suitable for use with most calendar systems. - * It is equivalent to: - *

    -     *  long lastDay = temporal.range(DAY_OF_YEAR).getMaximum();
    -     *  temporal.with(DAY_OF_YEAR, lastDay);
    -     * 
    - * - * @return the last day-of-year adjuster, not null - */ - public static TemporalAdjuster lastDayOfYear() { - return Impl.LAST_DAY_OF_YEAR; - } - - /** - * Returns the "first day of next year" adjuster, which returns a new date set to - * the first day of the next year. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 will return 2012-01-01. - *

    - * The behavior is suitable for use with most calendar systems. - * It is equivalent to: - *

    -     *  temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS);
    -     * 
    - * - * @return the first day of next month adjuster, not null - */ - public static TemporalAdjuster firstDayOfNextYear() { - return Impl.FIRST_DAY_OF_NEXT_YEAR; - } - - //----------------------------------------------------------------------- - /** - * Enum implementing the adjusters. - */ - private static class Impl implements TemporalAdjuster { - /** First day of month adjuster. */ - private static final Impl FIRST_DAY_OF_MONTH = new Impl(0); - /** Last day of month adjuster. */ - private static final Impl LAST_DAY_OF_MONTH = new Impl(1); - /** First day of next month adjuster. */ - private static final Impl FIRST_DAY_OF_NEXT_MONTH = new Impl(2); - /** First day of year adjuster. */ - private static final Impl FIRST_DAY_OF_YEAR = new Impl(3); - /** Last day of year adjuster. */ - private static final Impl LAST_DAY_OF_YEAR = new Impl(4); - /** First day of next month adjuster. */ - private static final Impl FIRST_DAY_OF_NEXT_YEAR = new Impl(5); - /** The ordinal. */ - private final int ordinal; - private Impl(int ordinal) { - this.ordinal = ordinal; - } - @Override - public Temporal adjustInto(Temporal temporal) { - switch (ordinal) { - case 0: return temporal.with(DAY_OF_MONTH, 1); - case 1: return temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum()); - case 2: return temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS); - case 3: return temporal.with(DAY_OF_YEAR, 1); - case 4: return temporal.with(DAY_OF_YEAR, temporal.range(DAY_OF_YEAR).getMaximum()); - case 5: return temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS); - } - throw new IllegalStateException("Unreachable"); - } - } - - //----------------------------------------------------------------------- - /** - * Returns the first in month adjuster, which returns a new date - * in the same month with the first matching day-of-week. - * This is used for expressions like 'first Tuesday in March'. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-12-15 for (MONDAY) will return 2011-12-05.
    - * The input 2011-12-15 for (FRIDAY) will return 2011-12-02.
    - *

    - * The behavior is suitable for use with most calendar systems. - * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields - * and the {@code DAYS} unit, and assumes a seven day week. - * - * @param dayOfWeek the day-of-week, not null - * @return the first in month adjuster, not null - */ - public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) { - Objects.requireNonNull(dayOfWeek, "dayOfWeek"); - return new DayOfWeekInMonth(1, dayOfWeek); - } - - /** - * Returns the last in month adjuster, which returns a new date - * in the same month with the last matching day-of-week. - * This is used for expressions like 'last Tuesday in March'. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-12-15 for (MONDAY) will return 2011-12-26.
    - * The input 2011-12-15 for (FRIDAY) will return 2011-12-30.
    - *

    - * The behavior is suitable for use with most calendar systems. - * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields - * and the {@code DAYS} unit, and assumes a seven day week. - * - * @param dayOfWeek the day-of-week, not null - * @return the first in month adjuster, not null - */ - public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) { - Objects.requireNonNull(dayOfWeek, "dayOfWeek"); - return new DayOfWeekInMonth(-1, dayOfWeek); - } - - /** - * Returns the day-of-week in month adjuster, which returns a new date - * in the same month with the ordinal day-of-week. - * This is used for expressions like the 'second Tuesday in March'. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-12-15 for (1,TUESDAY) will return 2011-12-06.
    - * The input 2011-12-15 for (2,TUESDAY) will return 2011-12-13.
    - * The input 2011-12-15 for (3,TUESDAY) will return 2011-12-20.
    - * The input 2011-12-15 for (4,TUESDAY) will return 2011-12-27.
    - * The input 2011-12-15 for (5,TUESDAY) will return 2012-01-03.
    - * The input 2011-12-15 for (-1,TUESDAY) will return 2011-12-27 (last in month).
    - * The input 2011-12-15 for (-4,TUESDAY) will return 2011-12-06 (3 weeks before last in month).
    - * The input 2011-12-15 for (-5,TUESDAY) will return 2011-11-29 (4 weeks before last in month).
    - * The input 2011-12-15 for (0,TUESDAY) will return 2011-11-29 (last in previous month).
    - *

    - * For a positive or zero ordinal, the algorithm is equivalent to finding the first - * day-of-week that matches within the month and then adding a number of weeks to it. - * For a negative ordinal, the algorithm is equivalent to finding the last - * day-of-week that matches within the month and then subtracting a number of weeks to it. - * The ordinal number of weeks is not validated and is interpreted leniently - * according to this algorithm. This definition means that an ordinal of zero finds - * the last matching day-of-week in the previous month. - *

    - * The behavior is suitable for use with most calendar systems. - * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields - * and the {@code DAYS} unit, and assumes a seven day week. - * - * @param ordinal the week within the month, unbound but typically from -5 to 5 - * @param dayOfWeek the day-of-week, not null - * @return the day-of-week in month adjuster, not null - * @throws IllegalArgumentException if the ordinal is invalid - */ - public static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) { - Objects.requireNonNull(dayOfWeek, "dayOfWeek"); - return new DayOfWeekInMonth(ordinal, dayOfWeek); - } - - /** - * Class implementing day-of-week in month adjuster. - */ - private static final class DayOfWeekInMonth implements TemporalAdjuster { - /** The ordinal. */ - private final int ordinal; - /** The day-of-week value, from 1 to 7. */ - private final int dowValue; - - private DayOfWeekInMonth(int ordinal, DayOfWeek dow) { - super(); - this.ordinal = ordinal; - this.dowValue = dow.getValue(); - } - @Override - public Temporal adjustInto(Temporal temporal) { - if (ordinal >= 0) { - Temporal temp = temporal.with(DAY_OF_MONTH, 1); - int curDow = temp.get(DAY_OF_WEEK); - int dowDiff = (dowValue - curDow + 7) % 7; - dowDiff += (ordinal - 1L) * 7L; // safe from overflow - return temp.plus(dowDiff, DAYS); - } else { - Temporal temp = temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum()); - int curDow = temp.get(DAY_OF_WEEK); - int daysDiff = dowValue - curDow; - daysDiff = (daysDiff == 0 ? 0 : (daysDiff > 0 ? daysDiff - 7 : daysDiff)); - daysDiff -= (-ordinal - 1L) * 7L; // safe from overflow - return temp.plus(daysDiff, DAYS); - } - } - } - - //----------------------------------------------------------------------- - /** - * Returns the next day-of-week adjuster, which adjusts the date to the - * first occurrence of the specified day-of-week after the date being adjusted. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
    - * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
    - * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-22 (seven days later). - *

    - * The behavior is suitable for use with most calendar systems. - * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, - * and assumes a seven day week. - * - * @param dayOfWeek the day-of-week to move the date to, not null - * @return the next day-of-week adjuster, not null - */ - public static TemporalAdjuster next(DayOfWeek dayOfWeek) { - return new RelativeDayOfWeek(2, dayOfWeek); - } - - /** - * Returns the next-or-same day-of-week adjuster, which adjusts the date to the - * first occurrence of the specified day-of-week after the date being adjusted - * unless it is already on that day in which case the same object is returned. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
    - * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
    - * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). - *

    - * The behavior is suitable for use with most calendar systems. - * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, - * and assumes a seven day week. - * - * @param dayOfWeek the day-of-week to check for or move the date to, not null - * @return the next-or-same day-of-week adjuster, not null - */ - public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) { - return new RelativeDayOfWeek(0, dayOfWeek); - } - - /** - * Returns the previous day-of-week adjuster, which adjusts the date to the - * first occurrence of the specified day-of-week before the date being adjusted. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
    - * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
    - * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-08 (seven days earlier). - *

    - * The behavior is suitable for use with most calendar systems. - * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, - * and assumes a seven day week. - * - * @param dayOfWeek the day-of-week to move the date to, not null - * @return the previous day-of-week adjuster, not null - */ - public static TemporalAdjuster previous(DayOfWeek dayOfWeek) { - return new RelativeDayOfWeek(3, dayOfWeek); - } - - /** - * Returns the previous-or-same day-of-week adjuster, which adjusts the date to the - * first occurrence of the specified day-of-week before the date being adjusted - * unless it is already on that day in which case the same object is returned. - *

    - * The ISO calendar system behaves as follows:
    - * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
    - * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
    - * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). - *

    - * The behavior is suitable for use with most calendar systems. - * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, - * and assumes a seven day week. - * - * @param dayOfWeek the day-of-week to check for or move the date to, not null - * @return the previous-or-same day-of-week adjuster, not null - */ - public static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) { - return new RelativeDayOfWeek(1, dayOfWeek); - } - - /** - * Implementation of next, previous or current day-of-week. - */ - private static final class RelativeDayOfWeek implements TemporalAdjuster { - /** Whether the current date is a valid answer. */ - private final int relative; - /** The day-of-week value, from 1 to 7. */ - private final int dowValue; - - private RelativeDayOfWeek(int relative, DayOfWeek dayOfWeek) { - Objects.requireNonNull(dayOfWeek, "dayOfWeek"); - this.relative = relative; - this.dowValue = dayOfWeek.getValue(); - } - - @Override - public Temporal adjustInto(Temporal temporal) { - int calDow = temporal.get(DAY_OF_WEEK); - if (relative < 2 && calDow == dowValue) { - return temporal; - } - if ((relative & 1) == 0) { - int daysDiff = calDow - dowValue; - return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); - } else { - int daysDiff = dowValue - calDow; - return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); - } - } - } - -} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/ChronoField.java --- a/src/share/classes/java/time/temporal/ChronoField.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/ChronoField.java Sat Apr 13 21:51:36 2013 +0100 @@ -76,6 +76,11 @@ import java.time.ZoneOffset; import java.time.chrono.ChronoLocalDate; import java.time.chrono.Chronology; +import java.util.Locale; +import java.util.Objects; +import java.util.ResourceBundle; +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.LocaleResources; /** * A standard set of fields. @@ -187,7 +192,7 @@ * This counts the second within the minute, from 0 to 59. * This field has the same meaning for all calendar systems. */ - SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59)), + SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second"), /** * The second-of-day. *

    @@ -201,7 +206,7 @@ * This counts the minute within the hour, from 0 to 59. * This field has the same meaning for all calendar systems. */ - MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59)), + MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute"), /** * The minute-of-day. *

    @@ -232,7 +237,7 @@ * This is the hour that would be observed on a standard 24-hour digital clock. * This field has the same meaning for all calendar systems. */ - HOUR_OF_DAY("HourOfDay", HOURS, DAYS, ValueRange.of(0, 23)), + HOUR_OF_DAY("HourOfDay", HOURS, DAYS, ValueRange.of(0, 23), "hour"), /** * The clock-hour-of-day. *

    @@ -247,7 +252,7 @@ * This counts the AM/PM within the day, from 0 (AM) to 1 (PM). * This field has the same meaning for all calendar systems. */ - AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1)), + AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod"), /** * The day-of-week, such as Tuesday. *

    @@ -263,7 +268,7 @@ * if they have a similar concept of named or numbered days within a period similar * to a week. It is recommended that the numbering starts from 1. */ - DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7)), + DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday"), /** * The aligned day-of-week within a month. *

    @@ -312,7 +317,7 @@ * day-of-month values for users of the calendar system. * Normally, this is a count of days from 1 to the length of the month. */ - DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31)), + DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day"), /** * The day-of-year. *

    @@ -377,17 +382,27 @@ * month-of-year values for users of the calendar system. * Normally, this is a count of months starting from 1. */ - MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12)), + MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month"), /** - * The epoch-month based on the Java epoch of 1970-01-01. + * The proleptic-month based, counting months sequentially from year 0. *

    - * This field is the sequential count of months where January 1970 (ISO) is zero. + * This field is the sequential count of months where the first month + * in proleptic-year zero has the value zero. + * Later months have increasingly larger values. + * Earlier months have increasingly small values. + * There are no gaps or breaks in the sequence of months. * Note that this uses the local time-line, ignoring offset and time-zone. *

    - * Non-ISO calendar systems should also implement this field to represent a sequential - * count of months. It is recommended to define zero as the month of 1970-01-01 (ISO). + * In the default ISO calendar system, June 2012 would have the value + * {@code (2012 * 12 + 6 - 1)}. This field is primarily for internal use. + *

    + * Non-ISO calendar systems must implement this field as per the definition above. + * It is just a simple zero-based count of elapsed months from the start of proleptic-year 0. + * All calendar systems with a full proleptic-year definition will have a year zero. + * If the calendar system has a minimum year that excludes year zero, then one must + * be extrapolated in order for this method to be defined. */ - EPOCH_MONTH("EpochMonth", MONTHS, FOREVER, ValueRange.of((Year.MIN_VALUE - 1970L) * 12, (Year.MAX_VALUE - 1970L) * 12L - 1L)), + PROLEPTIC_MONTH("ProlepticMonth", MONTHS, FOREVER, ValueRange.of(Year.MIN_VALUE * 12L, Year.MAX_VALUE * 12L + 11)), /** * The year within the era. *

    @@ -446,7 +461,7 @@ * defined with any appropriate value, although defining it to be the same as ISO may be * the best option. */ - YEAR("Year", YEARS, FOREVER, ValueRange.of(Year.MIN_VALUE, Year.MAX_VALUE)), + YEAR("Year", YEARS, FOREVER, ValueRange.of(Year.MIN_VALUE, Year.MAX_VALUE), "year"), /** * The era. *

    @@ -463,7 +478,7 @@ * Earlier eras must have sequentially smaller values. * Later eras must have sequentially larger values, */ - ERA("Era", ERAS, FOREVER, ValueRange.of(0, 1)), + ERA("Era", ERAS, FOREVER, ValueRange.of(0, 1), "era"), /** * The instant epoch-seconds. *

    @@ -499,12 +514,23 @@ private final TemporalUnit baseUnit; private final TemporalUnit rangeUnit; private final ValueRange range; + private final String displayNameKey; private ChronoField(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) { this.name = name; this.baseUnit = baseUnit; this.rangeUnit = rangeUnit; this.range = range; + this.displayNameKey = null; + } + + private ChronoField(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit, + ValueRange range, String displayNameKey) { + this.name = name; + this.baseUnit = baseUnit; + this.rangeUnit = rangeUnit; + this.range = range; + this.displayNameKey = displayNameKey; } //----------------------------------------------------------------------- @@ -514,6 +540,20 @@ } @Override + public String getDisplayName(Locale locale) { + Objects.requireNonNull(locale, "locale"); + if (displayNameKey == null) { + return getName(); + } + + LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() + .getLocaleResources(locale); + ResourceBundle rb = lr.getJavaTimeFormatData(); + String key = "field." + displayNameKey; + return rb.containsKey(key) ? rb.getString(key) : getName(); + } + + @Override public TemporalUnit getBaseUnit() { return baseUnit; } @@ -548,19 +588,25 @@ //----------------------------------------------------------------------- /** * Checks if this field represents a component of a date. + *

    + * Fields from day-of-week to era are date-based. * * @return true if it is a component of a date */ - public boolean isDateField() { + @Override + public boolean isDateBased() { return ordinal() >= DAY_OF_WEEK.ordinal() && ordinal() <= ERA.ordinal(); } /** * Checks if this field represents a component of a time. + *

    + * Fields from nano-of-second to am-pm-of-day are time-based. * * @return true if it is a component of a time */ - public boolean isTimeField() { + @Override + public boolean isTimeBased() { return ordinal() < DAY_OF_WEEK.ordinal(); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/ChronoUnit.java --- a/src/share/classes/java/time/temporal/ChronoUnit.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/ChronoUnit.java Sat Apr 13 21:51:36 2013 +0100 @@ -178,7 +178,7 @@ * Unit that represents the concept of an era. * The ISO calendar system doesn't have eras thus it is impossible to add * an era to a date or date-time. - * The estimated duration of the era is artificially defined as {@code 1,000,00,000 Years}. + * The estimated duration of the era is artificially defined as {@code 1,000,000,000 Years}. *

    * When used with other calendar systems there are no restrictions on the unit. */ diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/IsoFields.java --- a/src/share/classes/java/time/temporal/IsoFields.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/IsoFields.java Sat Apr 13 21:51:36 2013 +0100 @@ -69,13 +69,19 @@ import static java.time.temporal.ChronoUnit.WEEKS; import static java.time.temporal.ChronoUnit.YEARS; -import java.time.DateTimeException; import java.time.Duration; import java.time.LocalDate; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; +import java.time.format.ResolverStyle; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.ResourceBundle; + +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.LocaleResources; /** * Fields and units specific to the ISO-8601 calendar system, @@ -162,6 +168,27 @@ * value from 1 to 92. If the quarter has less than 92 days, then day 92, and * potentially day 91, is in the following quarter. *

    + * In the resolving phase of parsing, a date can be created from a year, + * quarter-of-year and day-of-quarter. + *

    + * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are + * validated against their range of valid values. The day-of-quarter field + * is validated from 1 to 90, 91 or 92 depending on the year and quarter. + *

    + * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are + * validated against their range of valid values. The day-of-quarter field is + * validated between 1 and 92, ignoring the actual range based on the year and quarter. + * If the day-of-quarter exceeds the actual range by one day, then the resulting date + * is one day later. If the day-of-quarter exceeds the actual range by two days, + * then the resulting date is two days later. + *

    + * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is validated + * against the range of valid values. The resulting date is calculated equivalent to + * the following three stage approach. First, create a date on the first of January + * in the requested year. Then take the quarter-of-year, subtract one, and add the + * amount in quarters to the date. Finally, take the day-of-quarter, subtract one, + * and add the amount in days to the date. + *

    * This unit is an immutable and thread-safe singleton. */ public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER; @@ -171,7 +198,11 @@ * This field allows the quarter-of-year value to be queried and set. * The quarter-of-year has values from 1 to 4. *

    - * The day-of-quarter can only be calculated if the month-of-year is available. + * The quarter-of-year can only be calculated if the month-of-year is available. + *

    + * In the resolving phase of parsing, a date can be created from a year, + * quarter-of-year and day-of-quarter. + * See {@link #DAY_OF_QUARTER} for details. *

    * This unit is an immutable and thread-safe singleton. */ @@ -180,6 +211,28 @@ * The field that represents the week-of-week-based-year. *

    * This field allows the week of the week-based-year value to be queried and set. + * The week-of-week-based-year has values from 1 to 52, or 53 if the + * week-based-year has 53 weeks. + *

    + * In the resolving phase of parsing, a date can be created from a + * week-based-year, week-of-week-based-year and day-of-week. + *

    + * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are + * validated against their range of valid values. The week-of-week-based-year + * field is validated from 1 to 52 or 53 depending on the week-based-year. + *

    + * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are + * validated against their range of valid values. The week-of-week-based-year + * field is validated between 1 and 53, ignoring the week-based-year. + * If the week-of-week-based-year is 53, but the week-based-year only has + * 52 weeks, then the resulting date is in week 1 of the following week-based-year. + *

    + * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the week-based-year + * is validated against the range of valid values. If the day-of-week is outside + * the range 1 to 7, then the resulting date is adjusted by a suitable number of + * weeks to reduce the day-of-week to the range 1 to 7. If the week-of-week-based-year + * value is outside the range 1 to 52, then any excess weeks are added or subtracted + * from the resulting date. *

    * This unit is an immutable and thread-safe singleton. */ @@ -189,6 +242,12 @@ *

    * This field allows the week-based-year value to be queried and set. *

    + * The field has a range that matches {@link LocalDate#MAX} and {@link LocalDate#MIN}. + *

    + * In the resolving phase of parsing, a date can be created from a + * week-based-year, week-of-week-based-year and day-of-week. + * See {@link #WEEK_OF_WEEK_BASED_YEAR} for details. + *

    * This unit is an immutable and thread-safe singleton. */ public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR; @@ -253,7 +312,7 @@ @Override public ValueRange rangeRefinedBy(TemporalAccessor temporal) { if (isSupportedBy(temporal) == false) { - throw new DateTimeException("Unsupported field: DayOfQuarter"); + throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter"); } long qoy = temporal.getLong(QUARTER_OF_YEAR); if (qoy == 1) { @@ -269,7 +328,7 @@ @Override public long getFrom(TemporalAccessor temporal) { if (isSupportedBy(temporal) == false) { - throw new DateTimeException("Unsupported field: DayOfQuarter"); + throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter"); } int doy = temporal.get(DAY_OF_YEAR); int moy = temporal.get(MONTH_OF_YEAR); @@ -285,16 +344,29 @@ return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue)); } @Override - public Map resolve(TemporalAccessor temporal, long value) { - if ((temporal.isSupported(YEAR) && temporal.isSupported(DAY_OF_QUARTER)) == false) { + public Map resolve(TemporalAccessor temporal, long doq, ResolverStyle resolverStyle) { + if ((temporal.isSupported(YEAR) && temporal.isSupported(QUARTER_OF_YEAR)) == false) { return null; } - int y = temporal.get(YEAR); - int qoy = temporal.get(QUARTER_OF_YEAR); - range().checkValidValue(value, this); // leniently check from 1 to 92 TODO: check - LocalDate date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1).plusDays(value - 1); + int y = temporal.get(YEAR); // validated + LocalDate date; + if (resolverStyle == ResolverStyle.LENIENT) { + long qoy = temporal.getLong(QUARTER_OF_YEAR); // unvalidated + date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(qoy, 1), 3)); + } else { + int qoy = temporal.get(QUARTER_OF_YEAR); // validated + date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1); + if (doq < 1 || doq > 90) { + if (resolverStyle == ResolverStyle.STRICT) { + rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range + } else { // SMART + range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter + } + } + } + long epochDay = Math.addExact(date.toEpochDay(), Math.subtractExact(doq, 1)); Map result = new HashMap<>(4, 1.0f); - result.put(EPOCH_DAY, date.toEpochDay()); + result.put(EPOCH_DAY, epochDay); result.put(YEAR, null); result.put(QUARTER_OF_YEAR, null); return result; @@ -324,7 +396,7 @@ @Override public long getFrom(TemporalAccessor temporal) { if (isSupportedBy(temporal) == false) { - throw new DateTimeException("Unsupported field: QuarterOfYear"); + throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear"); } long moy = temporal.getLong(MONTH_OF_YEAR); return ((moy + 2) / 3); @@ -343,6 +415,16 @@ public String getName() { return "WeekOfWeekBasedYear"; } + + @Override + public String getDisplayName(Locale locale) { + Objects.requireNonNull(locale, "locale"); + LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() + .getLocaleResources(locale); + ResourceBundle rb = lr.getJavaTimeFormatData(); + return rb.containsKey("field.week") ? rb.getString("field.week") : getName(); + } + @Override public TemporalUnit getBaseUnit() { return WEEKS; @@ -362,14 +444,14 @@ @Override public ValueRange rangeRefinedBy(TemporalAccessor temporal) { if (isSupportedBy(temporal) == false) { - throw new DateTimeException("Unsupported field: WeekOfWeekBasedYear"); + throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear"); } return getWeekRange(LocalDate.from(temporal)); } @Override public long getFrom(TemporalAccessor temporal) { if (isSupportedBy(temporal) == false) { - throw new DateTimeException("Unsupported field: WeekOfWeekBasedYear"); + throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear"); } return getWeek(LocalDate.from(temporal)); } @@ -381,14 +463,33 @@ return (R) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS); } @Override - public Map resolve(TemporalAccessor temporal, long value) { + public Map resolve(TemporalAccessor temporal, long wowby, ResolverStyle resolverStyle) { if ((temporal.isSupported(WEEK_BASED_YEAR) && temporal.isSupported(DAY_OF_WEEK)) == false) { return null; } - int wby = temporal.get(WEEK_BASED_YEAR); - int dow = temporal.get(DAY_OF_WEEK); - range().checkValidValue(value, this); // lenient range - LocalDate date = LocalDate.of(wby, 1, 4).plusWeeks(value - 1).with(DAY_OF_WEEK, dow); + int wby = temporal.get(WEEK_BASED_YEAR); // validated + LocalDate date = LocalDate.of(wby, 1, 4); + if (resolverStyle == ResolverStyle.LENIENT) { + long dow = temporal.getLong(DAY_OF_WEEK); // unvalidated + if (dow > 7) { + date = date.plusWeeks((dow - 1) / 7); + dow = ((dow - 1) % 7) + 1; + } else if (dow < 1) { + date = date.plusWeeks(Math.subtractExact(dow, 7) / 7); + dow = ((dow + 6) % 7) + 1; + } + date = date.plusWeeks(Math.subtractExact(wowby, 1)).with(DAY_OF_WEEK, dow); + } else { + int dow = temporal.get(DAY_OF_WEEK); // validated + if (wowby < 1 || wowby > 52) { + if (resolverStyle == ResolverStyle.STRICT) { + getWeekRange(date).checkValidValue(wowby, this); // only allow exact range + } else { // SMART + range().checkValidValue(wowby, this); // allow 1-53 rolling into next year + } + } + date = date.plusWeeks(wowby - 1).with(DAY_OF_WEEK, dow); + } Map result = new HashMap<>(2, 1.0f); result.put(EPOCH_DAY, date.toEpochDay()); result.put(WEEK_BASED_YEAR, null); @@ -420,7 +521,7 @@ @Override public long getFrom(TemporalAccessor temporal) { if (isSupportedBy(temporal) == false) { - throw new DateTimeException("Unsupported field: WeekBasedYear"); + throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear"); } return getWeekBasedYear(LocalDate.from(temporal)); } @@ -428,7 +529,7 @@ @Override public R adjustInto(R temporal, long newValue) { if (isSupportedBy(temporal) == false) { - throw new DateTimeException("Unsupported field: WeekBasedYear"); + throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear"); } int newVal = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check LocalDate date = LocalDate.from(temporal); @@ -439,6 +540,11 @@ }; @Override + public boolean isDateBased() { + return true; + } + + @Override public ValueRange rangeRefinedBy(TemporalAccessor temporal) { return range(); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/JulianFields.java --- a/src/share/classes/java/time/temporal/JulianFields.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/JulianFields.java Sat Apr 13 21:51:36 2013 +0100 @@ -66,6 +66,7 @@ import static java.time.temporal.ChronoUnit.FOREVER; import java.time.DateTimeException; +import java.time.format.ResolverStyle; import java.util.Collections; import java.util.Map; @@ -106,7 +107,12 @@ * When 'JULIAN_DAY.adjustInto()' is applied to a date-time, the time of day portion remains unaltered. * 'JULIAN_DAY.adjustInto()' and 'JULIAN_DAY.getFrom()' only apply to {@code Temporal} objects that * can be converted into {@link ChronoField#EPOCH_DAY}. - * A {@link DateTimeException} is thrown for any other type of object. + * An {@link UnsupportedTemporalTypeException} is thrown for any other type of object. + *

    + * In the resolving phase of parsing, a date can be created from a Julian Day field. + * In {@linkplain ResolverStyle#STRICT strict mode} and {@linkplain ResolverStyle#SMART smart mode} + * the Julian Day value is validated against the range of valid values. + * In {@linkplain ResolverStyle#LENIENT lenient mode} no validation occurs. *

    *

    Astronomical and Scientific Notes

    * The standard astronomical definition uses a fraction to indicate the time-of-day, @@ -147,10 +153,15 @@ * When 'MODIFIED_JULIAN_DAY.adjustInto()' is applied to a date-time, the time of day portion remains unaltered. * 'MODIFIED_JULIAN_DAY.adjustInto()' and 'MODIFIED_JULIAN_DAY.getFrom()' only apply to {@code Temporal} objects * that can be converted into {@link ChronoField#EPOCH_DAY}. - * A {@link DateTimeException} is thrown for any other type of object. + * An {@link UnsupportedTemporalTypeException} is thrown for any other type of object. *

    * This implementation is an integer version of MJD with the decimal part rounded to floor. *

    + * In the resolving phase of parsing, a date can be created from a Modified Julian Day field. + * In {@linkplain ResolverStyle#STRICT strict mode} and {@linkplain ResolverStyle#SMART smart mode} + * the Modified Julian Day value is validated against the range of valid values. + * In {@linkplain ResolverStyle#LENIENT lenient mode} no validation occurs. + *

    *

    Astronomical and Scientific Notes

    *
          *  | ISO date          | Modified Julian Day |      Decimal MJD |
    @@ -180,7 +191,12 @@
          * When 'RATA_DIE.adjustInto()' is applied to a date-time, the time of day portion remains unaltered.
          * 'RATA_DIE.adjustInto()' and 'RATA_DIE.getFrom()' only apply to {@code Temporal} objects
          * that can be converted into {@link ChronoField#EPOCH_DAY}.
    -     * A {@link DateTimeException} is thrown for any other type of object.
    +     * An {@link UnsupportedTemporalTypeException} is thrown for any other type of object.
    +     * 

    + * In the resolving phase of parsing, a date can be created from a Rata Die field. + * In {@linkplain ResolverStyle#STRICT strict mode} and {@linkplain ResolverStyle#SMART smart mode} + * the Rata Die value is validated against the range of valid values. + * In {@linkplain ResolverStyle#LENIENT lenient mode} no validation occurs. */ public static final TemporalField RATA_DIE = Field.RATA_DIE; @@ -232,6 +248,11 @@ } @Override + public boolean isDateBased() { + return true; + } + + @Override public ValueRange range() { return range; } @@ -266,8 +287,15 @@ //----------------------------------------------------------------------- @Override - public Map resolve(TemporalAccessor temporal, long value) { - return Collections.singletonMap(EPOCH_DAY, Math.subtractExact(value, offset)); + public Map resolve(TemporalAccessor temporal, long value, ResolverStyle resolverStyle) { + long epochDay; + if (resolverStyle == ResolverStyle.LENIENT) { + epochDay = Math.subtractExact(value, offset); + } else { + range().checkValidValue(value, this); + epochDay = value - offset; + } + return Collections.singletonMap(EPOCH_DAY, epochDay); } //----------------------------------------------------------------------- diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/Queries.java --- a/src/share/classes/java/time/temporal/Queries.java Sat Apr 13 20:16:00 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,356 +0,0 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * This file is available under and governed by the GNU General Public - * License version 2 only, as published by the Free Software Foundation. - * However, the following notice accompanied the original version of this - * file: - * - * Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of JSR-310 nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package java.time.temporal; - -import static java.time.temporal.ChronoField.EPOCH_DAY; -import static java.time.temporal.ChronoField.NANO_OF_DAY; -import static java.time.temporal.ChronoField.OFFSET_SECONDS; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.chrono.Chronology; - -/** - * Common implementations of {@code TemporalQuery}. - *

    - * This class provides common implementations of {@link TemporalQuery}. - * These queries are primarily used as optimizations, allowing the internals - * of other objects to be extracted effectively. Note that application code - * can also use the {@code from(TemporalAccessor)} method on most temporal - * objects as a method reference matching the query interface, such as - * {@code LocalDate::from} and {@code ZoneId::from}. - *

    - * There are two equivalent ways of using a {@code TemporalQuery}. - * The first is to invoke the method on the interface directly. - * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: - *

    - *   // these two lines are equivalent, but the second approach is recommended
    - *   dateTime = query.queryFrom(dateTime);
    - *   dateTime = dateTime.query(query);
    - * 
    - * It is recommended to use the second approach, {@code query(TemporalQuery)}, - * as it is a lot clearer to read in code. - * - *

    Specification for implementors

    - * This is a thread-safe utility class. - * All returned adjusters are immutable and thread-safe. - * - * @since 1.8 - */ -public final class Queries { - // note that it is vital that each method supplies a constant, not a - // calculated value, as they will be checked for using == - // it is also vital that each constant is different (due to the == checking) - // as such, alterations to use lambdas must be done with extreme care - - /** - * Private constructor since this is a utility class. - */ - private Queries() { - } - - //----------------------------------------------------------------------- - // special constants should be used to extract information from a TemporalAccessor - // that cannot be derived in other ways - // Javadoc added here, so as to pretend they are more normal than they really are - - /** - * A strict query for the {@code ZoneId}. - *

    - * This queries a {@code TemporalAccessor} for the zone. - * The zone is only returned if the date-time conceptually contains a {@code ZoneId}. - * It will not be returned if the date-time only conceptually has an {@code ZoneOffset}. - * Thus a {@link ZonedDateTime} will return the result of {@code getZone()}, - * but an {@link OffsetDateTime} will return null. - *

    - * In most cases, applications should use {@link #ZONE} as this query is too strict. - *

    - * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
    - * {@code LocalDate} returns null
    - * {@code LocalTime} returns null
    - * {@code LocalDateTime} returns null
    - * {@code ZonedDateTime} returns the associated zone
    - * {@code OffsetTime} returns null
    - * {@code OffsetDateTime} returns null
    - * {@code ChronoLocalDate} returns null
    - * {@code ChronoLocalDateTime} returns null
    - * {@code ChronoZonedDateTime} returns the associated zone
    - * {@code Era} returns null
    - * {@code DayOfWeek} returns null
    - * {@code Month} returns null
    - * {@code Year} returns null
    - * {@code YearMonth} returns null
    - * {@code MonthDay} returns null
    - * {@code ZoneOffset} returns null
    - * {@code Instant} returns null
    - * - * @return a query that can obtain the zone ID of a temporal, not null - */ - public static final TemporalQuery zoneId() { - return ZONE_ID; - } - static final TemporalQuery ZONE_ID = (temporal) -> { - return temporal.query(ZONE_ID); - }; - - /** - * A query for the {@code Chronology}. - *

    - * This queries a {@code TemporalAccessor} for the chronology. - * If the target {@code TemporalAccessor} represents a date, or part of a date, - * then it should return the chronology that the date is expressed in. - * As a result of this definition, objects only representing time, such as - * {@code LocalTime}, will return null. - *

    - * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
    - * {@code LocalDate} returns {@code IsoChronology.INSTANCE}
    - * {@code LocalTime} returns null (does not represent a date)
    - * {@code LocalDateTime} returns {@code IsoChronology.INSTANCE}
    - * {@code ZonedDateTime} returns {@code IsoChronology.INSTANCE}
    - * {@code OffsetTime} returns null (does not represent a date)
    - * {@code OffsetDateTime} returns {@code IsoChronology.INSTANCE}
    - * {@code ChronoLocalDate} returns the associated chronology
    - * {@code ChronoLocalDateTime} returns the associated chronology
    - * {@code ChronoZonedDateTime} returns the associated chronology
    - * {@code Era} returns the associated chronology
    - * {@code DayOfWeek} returns null (shared across chronologies)
    - * {@code Month} returns {@code IsoChronology.INSTANCE}
    - * {@code Year} returns {@code IsoChronology.INSTANCE}
    - * {@code YearMonth} returns {@code IsoChronology.INSTANCE}
    - * {@code MonthDay} returns null {@code IsoChronology.INSTANCE}
    - * {@code ZoneOffset} returns null (does not represent a date)
    - * {@code Instant} returns null (does not represent a date)
    - *

    - * The method {@link Chronology#from(TemporalAccessor)} can be used as a - * {@code TemporalQuery} via a method reference, {@code Chronology::from}. - * That method is equivalent to this query, except that it throws an - * exception if a chronology cannot be obtained. - * - * @return a query that can obtain the chronology of a temporal, not null - */ - public static final TemporalQuery chronology() { - return CHRONO; - } - static final TemporalQuery CHRONO = (temporal) -> { - return temporal.query(CHRONO); - }; - - /** - * A query for the smallest supported unit. - *

    - * This queries a {@code TemporalAccessor} for the time precision. - * If the target {@code TemporalAccessor} represents a consistent or complete date-time, - * date or time then this must return the smallest precision actually supported. - * Note that fields such as {@code NANO_OF_DAY} and {@code NANO_OF_SECOND} - * are defined to always return ignoring the precision, thus this is the only - * way to find the actual smallest supported unit. - * For example, were {@code GregorianCalendar} to implement {@code TemporalAccessor} - * it would return a precision of {@code MILLIS}. - *

    - * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
    - * {@code LocalDate} returns {@code DAYS}
    - * {@code LocalTime} returns {@code NANOS}
    - * {@code LocalDateTime} returns {@code NANOS}
    - * {@code ZonedDateTime} returns {@code NANOS}
    - * {@code OffsetTime} returns {@code NANOS}
    - * {@code OffsetDateTime} returns {@code NANOS}
    - * {@code ChronoLocalDate} returns {@code DAYS}
    - * {@code ChronoLocalDateTime} returns {@code NANOS}
    - * {@code ChronoZonedDateTime} returns {@code NANOS}
    - * {@code Era} returns {@code ERAS}
    - * {@code DayOfWeek} returns {@code DAYS}
    - * {@code Month} returns {@code MONTHS}
    - * {@code Year} returns {@code YEARS}
    - * {@code YearMonth} returns {@code MONTHS}
    - * {@code MonthDay} returns null (does not represent a complete date or time)
    - * {@code ZoneOffset} returns null (does not represent a date or time)
    - * {@code Instant} returns {@code NANOS}
    - * - * @return a query that can obtain the precision of a temporal, not null - */ - public static final TemporalQuery precision() { - return PRECISION; - } - static final TemporalQuery PRECISION = (temporal) -> { - return temporal.query(PRECISION); - }; - - //----------------------------------------------------------------------- - // non-special constants are standard queries that derive information from other information - /** - * A lenient query for the {@code ZoneId}, falling back to the {@code ZoneOffset}. - *

    - * This queries a {@code TemporalAccessor} for the zone. - * It first tries to obtain the zone, using {@link #zoneId()}. - * If that is not found it tries to obtain the {@link #offset()}. - * Thus a {@link ZonedDateTime} will return the result of {@code getZone()}, - * while an {@link OffsetDateTime} will return the result of {@code getOffset()}. - *

    - * In most cases, applications should use this query rather than {@code #zoneId()}. - *

    - * The method {@link ZoneId#from(TemporalAccessor)} can be used as a - * {@code TemporalQuery} via a method reference, {@code ZoneId::from}. - * That method is equivalent to this query, except that it throws an - * exception if a zone cannot be obtained. - * - * @return a query that can obtain the zone ID or offset of a temporal, not null - */ - public static final TemporalQuery zone() { - return ZONE; - } - static final TemporalQuery ZONE = (temporal) -> { - ZoneId zone = temporal.query(ZONE_ID); - return (zone != null ? zone : temporal.query(OFFSET)); - }; - - /** - * A query for {@code ZoneOffset} returning null if not found. - *

    - * This returns a {@code TemporalQuery} that can be used to query a temporal - * object for the offset. The query will return null if the temporal - * object cannot supply an offset. - *

    - * The query implementation examines the {@link ChronoField#OFFSET_SECONDS OFFSET_SECONDS} - * field and uses it to create a {@code ZoneOffset}. - *

    - * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a - * {@code TemporalQuery} via a method reference, {@code ZoneOffset::from}. - * This query and {@code ZoneOffset::from} will return the same result if the - * temporal object contains an offset. If the temporal object does not contain - * an offset, then the method reference will throw an exception, whereas this - * query will return null. - * - * @return a query that can obtain the offset of a temporal, not null - */ - public static final TemporalQuery offset() { - return OFFSET; - } - static final TemporalQuery OFFSET = (temporal) -> { - if (temporal.isSupported(OFFSET_SECONDS)) { - return ZoneOffset.ofTotalSeconds(temporal.get(OFFSET_SECONDS)); - } - return null; - }; - - /** - * A query for {@code LocalDate} returning null if not found. - *

    - * This returns a {@code TemporalQuery} that can be used to query a temporal - * object for the local date. The query will return null if the temporal - * object cannot supply a local date. - *

    - * The query implementation examines the {@link ChronoField#EPOCH_DAY EPOCH_DAY} - * field and uses it to create a {@code LocalDate}. - *

    - * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a - * {@code TemporalQuery} via a method reference, {@code LocalDate::from}. - * This query and {@code LocalDate::from} will return the same result if the - * temporal object contains a date. If the temporal object does not contain - * a date, then the method reference will throw an exception, whereas this - * query will return null. - * - * @return a query that can obtain the date of a temporal, not null - */ - public static final TemporalQuery localDate() { - return LOCAL_DATE; - } - static final TemporalQuery LOCAL_DATE = (temporal) -> { - if (temporal.isSupported(EPOCH_DAY)) { - return LocalDate.ofEpochDay(temporal.getLong(EPOCH_DAY)); - } - return null; - }; - - /** - * A query for {@code LocalTime} returning null if not found. - *

    - * This returns a {@code TemporalQuery} that can be used to query a temporal - * object for the local time. The query will return null if the temporal - * object cannot supply a local time. - *

    - * The query implementation examines the {@link ChronoField#NANO_OF_DAY NANO_OF_DAY} - * field and uses it to create a {@code LocalTime}. - *

    - * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a - * {@code TemporalQuery} via a method reference, {@code LocalTime::from}. - * This query and {@code LocalTime::from} will return the same result if the - * temporal object contains a time. If the temporal object does not contain - * a time, then the method reference will throw an exception, whereas this - * query will return null. - * - * @return a query that can obtain the time of a temporal, not null - */ - public static final TemporalQuery localTime() { - return LOCAL_TIME; - } - static final TemporalQuery LOCAL_TIME = (temporal) -> { - if (temporal.isSupported(NANO_OF_DAY)) { - return LocalTime.ofNanoOfDay(temporal.getLong(NANO_OF_DAY)); - } - return null; - }; - -} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/Temporal.java --- a/src/share/classes/java/time/temporal/Temporal.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/Temporal.java Sat Apr 13 21:51:36 2013 +0100 @@ -83,7 +83,7 @@ * Two pieces of date/time information cannot be represented by numbers, * the {@linkplain java.time.chrono.Chronology chronology} and the {@linkplain ZoneId time-zone}. * These can be accessed via {@link #query(TemporalQuery) queries} using - * the static methods defined on {@link Queries}. + * the static methods defined on {@link TemporalQuery}. *

    * This interface is a framework-level interface that should not be widely * used in application code. Instead, applications should create and pass @@ -134,7 +134,7 @@ * This adjusts this date-time according to the rules of the specified adjuster. * A simple adjuster might simply set the one of the fields, such as the year field. * A more complex adjuster might set the date to the last day of the month. - * A selection of common adjustments is provided in {@link Adjusters}. + * A selection of common adjustments is provided in {@link TemporalAdjuster}. * These include finding the "last day of the month" and "next Wednesday". * The adjuster is responsible for handling special cases, such as the varying * lengths of month and leap years. @@ -161,7 +161,7 @@ * @throws DateTimeException if unable to make the adjustment * @throws ArithmeticException if numeric overflow occurs */ - public default Temporal with(TemporalAdjuster adjuster) { + default Temporal with(TemporalAdjuster adjuster) { return adjuster.adjustInto(this); } @@ -180,7 +180,7 @@ *

    Specification for implementors

    * Implementations must check and handle all fields defined in {@link ChronoField}. * If the field is supported, then the adjustment must be performed. - * If unsupported, then a {@code DateTimeException} must be thrown. + * If unsupported, then an {@code UnsupportedTemporalTypeException} must be thrown. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} @@ -194,6 +194,7 @@ * @param newValue the new value of the field in the result * @return an object of the same type with the specified field set, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ Temporal with(TemporalField field, long newValue); @@ -210,7 +211,6 @@ *

          *  date = date.plus(period);                      // add a Period instance
          *  date = date.plus(duration);                    // add a Duration instance
    -     *  date = date.plus(MONTHS.between(start, end));  // static import of MONTHS field
          *  date = date.plus(workingDays(6));              // example user-written workingDays method
          * 
    *

    @@ -232,7 +232,7 @@ * @throws DateTimeException if the addition cannot be made * @throws ArithmeticException if numeric overflow occurs */ - public default Temporal plus(TemporalAmount amount) { + default Temporal plus(TemporalAmount amount) { return amount.addTo(this); } @@ -255,7 +255,7 @@ *

    Specification for implementors

    * Implementations must check and handle all units defined in {@link ChronoUnit}. * If the unit is supported, then the addition must be performed. - * If unsupported, then a {@code DateTimeException} must be thrown. + * If unsupported, then an {@code UnsupportedTemporalTypeException} must be thrown. *

    * If the unit is not a {@code ChronoUnit}, then the result of this method * is obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} @@ -269,6 +269,7 @@ * @param unit the unit of the period to add, not null * @return an object of the same type with the specified period added, not null * @throws DateTimeException if the unit cannot be added + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ Temporal plus(long amountToAdd, TemporalUnit unit); @@ -285,7 +286,6 @@ *

          *  date = date.minus(period);                      // subtract a Period instance
          *  date = date.minus(duration);                    // subtract a Duration instance
    -     *  date = date.minus(MONTHS.between(start, end));  // static import of MONTHS field
          *  date = date.minus(workingDays(6));              // example user-written workingDays method
          * 
    *

    @@ -307,7 +307,7 @@ * @throws DateTimeException if the subtraction cannot be made * @throws ArithmeticException if numeric overflow occurs */ - public default Temporal minus(TemporalAmount amount) { + default Temporal minus(TemporalAmount amount) { return amount.subtractFrom(this); } @@ -344,9 +344,10 @@ * @param unit the unit of the period to subtract, not null * @return an object of the same type with the specified period subtracted, not null * @throws DateTimeException if the unit cannot be subtracted + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ - public default Temporal minus(long amountToSubtract, TemporalUnit unit) { + default Temporal minus(long amountToSubtract, TemporalUnit unit) { return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); } @@ -388,7 +389,7 @@ * Implementations must begin by checking to ensure that the input temporal * object is of the same observable type as the implementation. * They must then perform the calculation for all instances of {@link ChronoUnit}. - * A {@code DateTimeException} must be thrown for {@code ChronoUnit} + * An {@code UnsupportedTemporalTypeException} must be thrown for {@code ChronoUnit} * instances that are unsupported. *

    * If the unit is not a {@code ChronoUnit}, then the result of this method @@ -401,7 +402,7 @@ * // check input temporal is the same type as this class * if (unit instanceof ChronoUnit) { * // if unit is supported, then calculate and return result - * // else throw DateTimeException for unsupported units + * // else throw UnsupportedTemporalTypeException for unsupported units * } * return unit.between(this, endTemporal); *

    @@ -414,6 +415,7 @@ * the unit; positive if the specified object is later than this one, negative if * it is earlier than this one * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported * @throws ArithmeticException if numeric overflow occurs */ long periodUntil(Temporal endTemporal, TemporalUnit unit); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalAccessor.java --- a/src/share/classes/java/time/temporal/TemporalAccessor.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/TemporalAccessor.java Sat Apr 13 21:51:36 2013 +0100 @@ -63,6 +63,7 @@ import java.time.DateTimeException; import java.time.ZoneId; +import java.util.Objects; /** * Framework-level interface defining read-only access to a temporal object, @@ -80,8 +81,8 @@ *

    * Two pieces of date/time information cannot be represented by numbers, * the {@linkplain java.time.chrono.Chronology chronology} and the {@linkplain ZoneId time-zone}. - * These can be accessed via {@link #query(TemporalQuery) queries} using - * the static methods defined on {@link Queries}. + * These can be accessed via {@linkplain #query(TemporalQuery) queries} using + * the static methods defined on {@link TemporalQuery}. *

    * A sub-interface, {@link Temporal}, extends this definition to one that also * supports adjustment and manipulation on more complete temporal objects. @@ -139,7 +140,7 @@ *

    Specification for implementors

    * Implementations must check and handle all fields defined in {@link ChronoField}. * If the field is supported, then the range of the field must be returned. - * If unsupported, then a {@code DateTimeException} must be thrown. + * If unsupported, then an {@code UnsupportedTemporalTypeException} must be thrown. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessorl)} @@ -153,7 +154,7 @@ * if (isSupported(field)) { * return field.range(); * } - * throw new DateTimeException("Unsupported field: " + field.getName()); + * throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); * } * return field.rangeRefinedBy(this); *

    @@ -161,14 +162,16 @@ * @param field the field to query the range for, not null * @return the range of valid values for the field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported */ - public default ValueRange range(TemporalField field) { + default ValueRange range(TemporalField field) { if (field instanceof ChronoField) { if (isSupported(field)) { return field.range(); } - throw new DateTimeException("Unsupported field: " + field.getName()); + throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName()); } + Objects.requireNonNull(field, "field"); return field.rangeRefinedBy(this); } @@ -184,28 +187,40 @@ * Implementations must check and handle all fields defined in {@link ChronoField}. * If the field is supported and has an {@code int} range, then the value of * the field must be returned. - * If unsupported, then a {@code DateTimeException} must be thrown. + * If unsupported, then an {@code UnsupportedTemporalTypeException} must be thrown. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} * passing {@code this} as the argument. *

    - * Implementations must not alter either this object. + * Implementations must not alter this object. *

    * The default implementation must behave equivalent to this code: *

    -     *  return range(field).checkValidIntValue(getLong(field), field);
    +     *  if (range(field).isIntValue()) {
    +     *    return range(field).checkValidIntValue(getLong(field), field);
    +     *  }
    +     *  throw new UnsupportedTemporalTypeException("Invalid field " + field + " + for get() method, use getLong() instead");
          * 
    * * @param field the field to get, not null * @return the value for the field, within the valid range of values - * @throws DateTimeException if a value for the field cannot be obtained - * @throws DateTimeException if the range of valid values for the field exceeds an {@code int} - * @throws DateTimeException if the value is outside the range of valid values for the field + * @throws DateTimeException if a value for the field cannot be obtained or + * the value is outside the range of valid values for the field + * @throws UnsupportedTemporalTypeException if the field is not supported or + * the range of values exceeds an {@code int} * @throws ArithmeticException if numeric overflow occurs */ - public default int get(TemporalField field) { - return range(field).checkValidIntValue(getLong(field), field); + default int get(TemporalField field) { + ValueRange range = range(field); + if (range.isIntValue() == false) { + throw new UnsupportedTemporalTypeException("Invalid field " + field + " + for get() method, use getLong() instead"); + } + long value = getLong(field); + if (range.isValidValue(value) == false) { + throw new DateTimeException("Invalid value for " + field + " (valid values " + range + "): " + value); + } + return (int) value; } /** @@ -219,7 +234,7 @@ *

    Specification for implementors

    * Implementations must check and handle all fields defined in {@link ChronoField}. * If the field is supported, then the value of the field must be returned. - * If unsupported, then a {@code DateTimeException} must be thrown. + * If unsupported, then an {@code UnsupportedTemporalTypeException} must be thrown. *

    * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} @@ -230,6 +245,7 @@ * @param field the field to get, not null * @return the value for the field * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ long getLong(TemporalField field); @@ -247,13 +263,13 @@ *

    * The most common query implementations are method references, such as * {@code LocalDate::from} and {@code ZoneId::from}. - * Further implementations are on {@link Queries}. - * Queries may also be defined by applications. + * Additional implementations are provided as static methods on {@link TemporalQuery}. * *

    Specification for implementors

    * The default implementation must behave equivalent to this code: *
    -     *  if (query == Queries.zoneId() || query == Queries.chronology() || query == Queries.precision()) {
    +     *  if (query == TemporalQuery.zoneId() ||
    +     *        query == TemporalQuery.chronology() || query == TemporalQuery.precision()) {
          *    return null;
          *  }
          *  return query.queryFrom(this);
    @@ -270,7 +286,7 @@
          * For example, an application-defined {@code HourMin} class storing the hour
          * and minute must override this method as follows:
          * 
    -     *  if (query == Queries.precision()) {
    +     *  if (query == TemporalQuery.precision()) {
          *    return MINUTES;
          *  }
          *  return TemporalAccessor.super.query(query);
    @@ -282,8 +298,8 @@
          * @throws DateTimeException if unable to query
          * @throws ArithmeticException if numeric overflow occurs
          */
    -    public default  R query(TemporalQuery query) {
    -        if (query == Queries.zoneId() || query == Queries.chronology() || query == Queries.precision()) {
    +    default  R query(TemporalQuery query) {
    +        if (query == TemporalQuery.zoneId() || query == TemporalQuery.chronology() || query == TemporalQuery.precision()) {
                 return null;
             }
             return query.queryFrom(this);
    diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalAdjuster.java
    --- a/src/share/classes/java/time/temporal/TemporalAdjuster.java	Sat Apr 13 20:16:00 2013 +0100
    +++ b/src/share/classes/java/time/temporal/TemporalAdjuster.java	Sat Apr 13 21:51:36 2013 +0100
    @@ -62,6 +62,9 @@
     package java.time.temporal;
     
     import java.time.DateTimeException;
    +import java.time.DayOfWeek;
    +import java.time.LocalDate;
    +import java.util.function.UnaryOperator;
     
     /**
      * Strategy for adjusting a temporal object.
    @@ -83,13 +86,22 @@
      * It is recommended to use the second approach, {@code with(TemporalAdjuster)},
      * as it is a lot clearer to read in code.
      * 

    - * See {@link Adjusters} for a standard set of adjusters, including finding the - * last day of the month. - * Adjusters may also be defined by applications. + * This class also contains a standard set of adjusters, available as static methods. + * These include: + *

      + *
    • finding the first or last day of the month + *
    • finding the first day of next month + *
    • finding the first or last day of the year + *
    • finding the first day of next year + *
    • finding the first or last day-of-week within a month, such as "first Wednesday in June" + *
    • finding the next or previous day-of-week, such as "next Thursday" + *
    * *

    Specification for implementors

    * This interface places no restrictions on the mutability of implementations, * however immutability is strongly recommended. + *

    + * All the implementations supplied by the static methods on this interface are immutable. * * @since 1.8 */ @@ -128,7 +140,7 @@ *

    * The input temporal object may be in a calendar system other than ISO. * Implementations may choose to document compatibility with other calendar systems, - * or reject non-ISO temporal objects by {@link Queries#chronology() querying the chronology}. + * or reject non-ISO temporal objects by {@link TemporalQuery#chronology() querying the chronology}. *

    * This method may be called from multiple threads in parallel. * It must be thread-safe when invoked. @@ -140,4 +152,311 @@ */ Temporal adjustInto(Temporal temporal); + //----------------------------------------------------------------------- + /** + * Obtains a {@code TemporalAdjuster} that wraps a date adjuster. + *

    + * The {@code TemporalAdjuster} is based on the low level {@code Temporal} interface. + * This method allows an adjustment from {@code LocalDate} to {@code LocalDate} + * to be wrapped to match the temporal-based interface. + * This is provided for convenience to make user-written adjusters simpler. + *

    + * In general, user-written adjusters should be static constants: + *

    +     *  static TemporalAdjuster TWO_DAYS_LATER = TemporalAdjuster.ofDateAdjuster(
    +     *    date -> date.plusDays(2));
    +     * 
    + * + * @param dateBasedAdjuster the date-based adjuster, not null + * @return the temporal adjuster wrapping on the date adjuster, not null + */ + static TemporalAdjuster ofDateAdjuster(UnaryOperator dateBasedAdjuster) { + return TemporalAdjusters.ofDateAdjuster(dateBasedAdjuster); + } + + //----------------------------------------------------------------------- + /** + * Returns the "first day of month" adjuster, which returns a new date set to + * the first day of the current month. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-01-01.
    + * The input 2011-02-15 will return 2011-02-01. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_MONTH, 1);
    +     * 
    + * + * @return the first day-of-month adjuster, not null + */ + static TemporalAdjuster firstDayOfMonth() { + return TemporalAdjusters.firstDayOfMonth(); + } + + /** + * Returns the "last day of month" adjuster, which returns a new date set to + * the last day of the current month. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-01-31.
    + * The input 2011-02-15 will return 2011-02-28.
    + * The input 2012-02-15 will return 2012-02-29 (leap year).
    + * The input 2011-04-15 will return 2011-04-30. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  long lastDay = temporal.range(DAY_OF_MONTH).getMaximum();
    +     *  temporal.with(DAY_OF_MONTH, lastDay);
    +     * 
    + * + * @return the last day-of-month adjuster, not null + */ + static TemporalAdjuster lastDayOfMonth() { + return TemporalAdjusters.lastDayOfMonth(); + } + + /** + * Returns the "first day of next month" adjuster, which returns a new date set to + * the first day of the next month. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-02-01.
    + * The input 2011-02-15 will return 2011-03-01. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS);
    +     * 
    + * + * @return the first day of next month adjuster, not null + */ + static TemporalAdjuster firstDayOfNextMonth() { + return TemporalAdjusters.firstDayOfNextMonth(); + } + + //----------------------------------------------------------------------- + /** + * Returns the "first day of year" adjuster, which returns a new date set to + * the first day of the current year. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-01-01.
    + * The input 2011-02-15 will return 2011-01-01.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_YEAR, 1);
    +     * 
    + * + * @return the first day-of-year adjuster, not null + */ + static TemporalAdjuster firstDayOfYear() { + return TemporalAdjusters.firstDayOfYear(); + } + + /** + * Returns the "last day of year" adjuster, which returns a new date set to + * the last day of the current year. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-12-31.
    + * The input 2011-02-15 will return 2011-12-31.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  long lastDay = temporal.range(DAY_OF_YEAR).getMaximum();
    +     *  temporal.with(DAY_OF_YEAR, lastDay);
    +     * 
    + * + * @return the last day-of-year adjuster, not null + */ + static TemporalAdjuster lastDayOfYear() { + return TemporalAdjusters.lastDayOfYear(); + } + + /** + * Returns the "first day of next year" adjuster, which returns a new date set to + * the first day of the next year. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2012-01-01. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS);
    +     * 
    + * + * @return the first day of next month adjuster, not null + */ + static TemporalAdjuster firstDayOfNextYear() { + return TemporalAdjusters.firstDayOfNextYear(); + } + + //----------------------------------------------------------------------- + /** + * Returns the first in month adjuster, which returns a new date + * in the same month with the first matching day-of-week. + * This is used for expressions like 'first Tuesday in March'. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-12-15 for (MONDAY) will return 2011-12-05.
    + * The input 2011-12-15 for (FRIDAY) will return 2011-12-02.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param dayOfWeek the day-of-week, not null + * @return the first in month adjuster, not null + */ + static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) { + return TemporalAdjuster.dayOfWeekInMonth(1, dayOfWeek); + } + + /** + * Returns the last in month adjuster, which returns a new date + * in the same month with the last matching day-of-week. + * This is used for expressions like 'last Tuesday in March'. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-12-15 for (MONDAY) will return 2011-12-26.
    + * The input 2011-12-15 for (FRIDAY) will return 2011-12-30.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param dayOfWeek the day-of-week, not null + * @return the first in month adjuster, not null + */ + static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) { + return TemporalAdjuster.dayOfWeekInMonth(-1, dayOfWeek); + } + + /** + * Returns the day-of-week in month adjuster, which returns a new date + * in the same month with the ordinal day-of-week. + * This is used for expressions like the 'second Tuesday in March'. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-12-15 for (1,TUESDAY) will return 2011-12-06.
    + * The input 2011-12-15 for (2,TUESDAY) will return 2011-12-13.
    + * The input 2011-12-15 for (3,TUESDAY) will return 2011-12-20.
    + * The input 2011-12-15 for (4,TUESDAY) will return 2011-12-27.
    + * The input 2011-12-15 for (5,TUESDAY) will return 2012-01-03.
    + * The input 2011-12-15 for (-1,TUESDAY) will return 2011-12-27 (last in month).
    + * The input 2011-12-15 for (-4,TUESDAY) will return 2011-12-06 (3 weeks before last in month).
    + * The input 2011-12-15 for (-5,TUESDAY) will return 2011-11-29 (4 weeks before last in month).
    + * The input 2011-12-15 for (0,TUESDAY) will return 2011-11-29 (last in previous month).
    + *

    + * For a positive or zero ordinal, the algorithm is equivalent to finding the first + * day-of-week that matches within the month and then adding a number of weeks to it. + * For a negative ordinal, the algorithm is equivalent to finding the last + * day-of-week that matches within the month and then subtracting a number of weeks to it. + * The ordinal number of weeks is not validated and is interpreted leniently + * according to this algorithm. This definition means that an ordinal of zero finds + * the last matching day-of-week in the previous month. + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param ordinal the week within the month, unbounded but typically from -5 to 5 + * @param dayOfWeek the day-of-week, not null + * @return the day-of-week in month adjuster, not null + */ + static TemporalAdjuster dayOfWeekInMonth(final int ordinal, DayOfWeek dayOfWeek) { + return TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek); + } + + //----------------------------------------------------------------------- + /** + * Returns the next day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week after the date being adjusted. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-22 (seven days later). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to move the date to, not null + * @return the next day-of-week adjuster, not null + */ + static TemporalAdjuster next(DayOfWeek dayOfWeek) { + return TemporalAdjusters.next(dayOfWeek); + } + + /** + * Returns the next-or-same day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week after the date being adjusted + * unless it is already on that day in which case the same object is returned. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to check for or move the date to, not null + * @return the next-or-same day-of-week adjuster, not null + */ + static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) { + return TemporalAdjusters.nextOrSame(dayOfWeek); + } + + /** + * Returns the previous day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week before the date being adjusted. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-08 (seven days earlier). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to move the date to, not null + * @return the previous day-of-week adjuster, not null + */ + static TemporalAdjuster previous(DayOfWeek dayOfWeek) { + return TemporalAdjusters.previous(dayOfWeek); + } + + /** + * Returns the previous-or-same day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week before the date being adjusted + * unless it is already on that day in which case the same object is returned. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to check for or move the date to, not null + * @return the previous-or-same day-of-week adjuster, not null + */ + static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) { + return TemporalAdjusters.previousOrSame(dayOfWeek); + } + } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalAdjusters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/time/temporal/TemporalAdjusters.java Sat Apr 13 21:51:36 2013 +0100 @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Copyright (c) 2012-2013, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package java.time.temporal; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.YEARS; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.Objects; +import java.util.function.UnaryOperator; + +/** + * Implementations of the static methods in {@code TemporalAdjuster} + * + * @since 1.8 + */ +final class TemporalAdjusters { + // work around compiler bug not allowing lambdas in static methods + + private TemporalAdjusters() { + } + + //----------------------------------------------------------------------- + /** + * Obtains a {@code TemporalAdjuster} that wraps a date adjuster. + *

    + * The {@code TemporalAdjuster} is based on the low level {@code Temporal} interface. + * This method allows an adjustment from {@code LocalDate} to {@code LocalDate} + * to be wrapped to match the temporal-based interface. + * This is provided for convenience to make user-written adjusters simpler. + *

    + * In general, user-written adjusters should be static constants: + *

    +     *  public static TemporalAdjuster TWO_DAYS_LATER = TemporalAdjuster.ofDateAdjuster(
    +     *    date -> date.plusDays(2));
    +     * 
    + * + * @param dateBasedAdjuster the date-based adjuster, not null + * @return the temporal adjuster wrapping on the date adjuster, not null + */ + static TemporalAdjuster ofDateAdjuster(UnaryOperator dateBasedAdjuster) { + Objects.requireNonNull(dateBasedAdjuster, "dateBasedAdjuster"); + return (temporal) -> { + LocalDate input = LocalDate.from(temporal); + LocalDate output = dateBasedAdjuster.apply(input); + return temporal.with(output); + }; + } + + //----------------------------------------------------------------------- + /** + * Returns the "first day of month" adjuster, which returns a new date set to + * the first day of the current month. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-01-01.
    + * The input 2011-02-15 will return 2011-02-01. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_MONTH, 1);
    +     * 
    + * + * @return the first day-of-month adjuster, not null + */ + static TemporalAdjuster firstDayOfMonth() { + return (temporal) -> temporal.with(DAY_OF_MONTH, 1); + } + + /** + * Returns the "last day of month" adjuster, which returns a new date set to + * the last day of the current month. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-01-31.
    + * The input 2011-02-15 will return 2011-02-28.
    + * The input 2012-02-15 will return 2012-02-29 (leap year).
    + * The input 2011-04-15 will return 2011-04-30. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  long lastDay = temporal.range(DAY_OF_MONTH).getMaximum();
    +     *  temporal.with(DAY_OF_MONTH, lastDay);
    +     * 
    + * + * @return the last day-of-month adjuster, not null + */ + static TemporalAdjuster lastDayOfMonth() { + return (temporal) -> temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum()); + } + + /** + * Returns the "first day of next month" adjuster, which returns a new date set to + * the first day of the next month. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-02-01.
    + * The input 2011-02-15 will return 2011-03-01. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS);
    +     * 
    + * + * @return the first day of next month adjuster, not null + */ + static TemporalAdjuster firstDayOfNextMonth() { + return (temporal) -> temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS); + } + + //----------------------------------------------------------------------- + /** + * Returns the "first day of year" adjuster, which returns a new date set to + * the first day of the current year. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-01-01.
    + * The input 2011-02-15 will return 2011-01-01.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_YEAR, 1);
    +     * 
    + * + * @return the first day-of-year adjuster, not null + */ + static TemporalAdjuster firstDayOfYear() { + return (temporal) -> temporal.with(DAY_OF_YEAR, 1); + } + + /** + * Returns the "last day of year" adjuster, which returns a new date set to + * the last day of the current year. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2011-12-31.
    + * The input 2011-02-15 will return 2011-12-31.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  long lastDay = temporal.range(DAY_OF_YEAR).getMaximum();
    +     *  temporal.with(DAY_OF_YEAR, lastDay);
    +     * 
    + * + * @return the last day-of-year adjuster, not null + */ + static TemporalAdjuster lastDayOfYear() { + return (temporal) -> temporal.with(DAY_OF_YEAR, temporal.range(DAY_OF_YEAR).getMaximum()); + } + + /** + * Returns the "first day of next year" adjuster, which returns a new date set to + * the first day of the next year. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 will return 2012-01-01. + *

    + * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

    +     *  temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS);
    +     * 
    + * + * @return the first day of next month adjuster, not null + */ + static TemporalAdjuster firstDayOfNextYear() { + return (temporal) -> temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS); + } + + //----------------------------------------------------------------------- + /** + * Returns the first in month adjuster, which returns a new date + * in the same month with the first matching day-of-week. + * This is used for expressions like 'first Tuesday in March'. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-12-15 for (MONDAY) will return 2011-12-05.
    + * The input 2011-12-15 for (FRIDAY) will return 2011-12-02.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param dayOfWeek the day-of-week, not null + * @return the first in month adjuster, not null + */ + static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) { + return TemporalAdjusters.dayOfWeekInMonth(1, dayOfWeek); + } + + /** + * Returns the last in month adjuster, which returns a new date + * in the same month with the last matching day-of-week. + * This is used for expressions like 'last Tuesday in March'. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-12-15 for (MONDAY) will return 2011-12-26.
    + * The input 2011-12-15 for (FRIDAY) will return 2011-12-30.
    + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param dayOfWeek the day-of-week, not null + * @return the first in month adjuster, not null + */ + static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) { + return TemporalAdjusters.dayOfWeekInMonth(-1, dayOfWeek); + } + + /** + * Returns the day-of-week in month adjuster, which returns a new date + * in the same month with the ordinal day-of-week. + * This is used for expressions like the 'second Tuesday in March'. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-12-15 for (1,TUESDAY) will return 2011-12-06.
    + * The input 2011-12-15 for (2,TUESDAY) will return 2011-12-13.
    + * The input 2011-12-15 for (3,TUESDAY) will return 2011-12-20.
    + * The input 2011-12-15 for (4,TUESDAY) will return 2011-12-27.
    + * The input 2011-12-15 for (5,TUESDAY) will return 2012-01-03.
    + * The input 2011-12-15 for (-1,TUESDAY) will return 2011-12-27 (last in month).
    + * The input 2011-12-15 for (-4,TUESDAY) will return 2011-12-06 (3 weeks before last in month).
    + * The input 2011-12-15 for (-5,TUESDAY) will return 2011-11-29 (4 weeks before last in month).
    + * The input 2011-12-15 for (0,TUESDAY) will return 2011-11-29 (last in previous month).
    + *

    + * For a positive or zero ordinal, the algorithm is equivalent to finding the first + * day-of-week that matches within the month and then adding a number of weeks to it. + * For a negative ordinal, the algorithm is equivalent to finding the last + * day-of-week that matches within the month and then subtracting a number of weeks to it. + * The ordinal number of weeks is not validated and is interpreted leniently + * according to this algorithm. This definition means that an ordinal of zero finds + * the last matching day-of-week in the previous month. + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param ordinal the week within the month, unbounded but typically from -5 to 5 + * @param dayOfWeek the day-of-week, not null + * @return the day-of-week in month adjuster, not null + */ + static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) { + Objects.requireNonNull(dayOfWeek, "dayOfWeek"); + int dowValue = dayOfWeek.getValue(); + if (ordinal >= 0) { + return (temporal) -> { + Temporal temp = temporal.with(DAY_OF_MONTH, 1); + int curDow = temp.get(DAY_OF_WEEK); + int dowDiff = (dowValue - curDow + 7) % 7; + dowDiff += (ordinal - 1L) * 7L; // safe from overflow + return temp.plus(dowDiff, DAYS); + }; + } else { + return (temporal) -> { + Temporal temp = temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum()); + int curDow = temp.get(DAY_OF_WEEK); + int daysDiff = dowValue - curDow; + daysDiff = (daysDiff == 0 ? 0 : (daysDiff > 0 ? daysDiff - 7 : daysDiff)); + daysDiff -= (-ordinal - 1L) * 7L; // safe from overflow + return temp.plus(daysDiff, DAYS); + }; + } + } + + //----------------------------------------------------------------------- + /** + * Returns the next day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week after the date being adjusted. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-22 (seven days later). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to move the date to, not null + * @return the next day-of-week adjuster, not null + */ + static TemporalAdjuster next(DayOfWeek dayOfWeek) { + int dowValue = dayOfWeek.getValue(); + return (temporal) -> { + int calDow = temporal.get(DAY_OF_WEEK); + int daysDiff = calDow - dowValue; + return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); + }; + } + + /** + * Returns the next-or-same day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week after the date being adjusted + * unless it is already on that day in which case the same object is returned. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to check for or move the date to, not null + * @return the next-or-same day-of-week adjuster, not null + */ + static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) { + int dowValue = dayOfWeek.getValue(); + return (temporal) -> { + int calDow = temporal.get(DAY_OF_WEEK); + if (calDow == dowValue) { + return temporal; + } + int daysDiff = calDow - dowValue; + return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); + }; + } + + /** + * Returns the previous day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week before the date being adjusted. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-08 (seven days earlier). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to move the date to, not null + * @return the previous day-of-week adjuster, not null + */ + static TemporalAdjuster previous(DayOfWeek dayOfWeek) { + int dowValue = dayOfWeek.getValue(); + return (temporal) -> { + int calDow = temporal.get(DAY_OF_WEEK); + int daysDiff = dowValue - calDow; + return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); + }; + } + + /** + * Returns the previous-or-same day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week before the date being adjusted + * unless it is already on that day in which case the same object is returned. + *

    + * The ISO calendar system behaves as follows:
    + * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
    + * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). + *

    + * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to check for or move the date to, not null + * @return the previous-or-same day-of-week adjuster, not null + */ + static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) { + int dowValue = dayOfWeek.getValue(); + return (temporal) -> { + int calDow = temporal.get(DAY_OF_WEEK); + if (calDow == dowValue) { + return temporal; + } + int daysDiff = dowValue - calDow; + return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); + }; + } + +} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalAmount.java --- a/src/share/classes/java/time/temporal/TemporalAmount.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/TemporalAmount.java Sat Apr 13 21:51:36 2013 +0100 @@ -75,7 +75,7 @@ * to any specific point on the time-line. *

    * The amount can be thought of as a {@code Map} of {@link TemporalUnit} to - * {@code long}, exposed via {@link #getUnits()}and {@link #get(TemporalUnit)}. + * {@code long}, exposed via {@link #getUnits()} and {@link #get(TemporalUnit)}. * A simple case might have a single unit-value pair, such as "6 hours". * A more complex case may have multiple unit-value pairs, such as * "7 years, 3 months and 5 days". @@ -111,9 +111,10 @@ * * @param unit the {@code TemporalUnit} for which to return the value * @return the long value of the unit - * @throws DateTimeException if the {@code unit} is not supported + * @throws DateTimeException if a value for the unit cannot be obtained + * @throws UnsupportedTemporalTypeException if the {@code unit} is not supported */ - public long get(TemporalUnit unit); + long get(TemporalUnit unit); /** * Returns the list of units uniquely defining the value of this TemporalAmount. @@ -130,7 +131,7 @@ * * @return the List of {@code TemporalUnits}; not null */ - public List getUnits(); + List getUnits(); /** * Adds to the specified temporal object. @@ -162,7 +163,7 @@ *

    * The input temporal object may be in a calendar system other than ISO. * Implementations may choose to document compatibility with other calendar systems, - * or reject non-ISO temporal objects by {@link Queries#chronology() querying the chronology}. + * or reject non-ISO temporal objects by {@link TemporalQuery#chronology() querying the chronology}. *

    * This method may be called from multiple threads in parallel. * It must be thread-safe when invoked. @@ -172,7 +173,7 @@ * @throws DateTimeException if unable to add * @throws ArithmeticException if numeric overflow occurs */ - public Temporal addTo(Temporal temporal); + Temporal addTo(Temporal temporal); /** * Subtracts this object from the specified temporal object. @@ -204,7 +205,7 @@ *

    * The input temporal object may be in a calendar system other than ISO. * Implementations may choose to document compatibility with other calendar systems, - * or reject non-ISO temporal objects by {@link Queries#chronology() querying the chronology}. + * or reject non-ISO temporal objects by {@link TemporalQuery#chronology() querying the chronology}. *

    * This method may be called from multiple threads in parallel. * It must be thread-safe when invoked. @@ -214,5 +215,5 @@ * @throws DateTimeException if unable to subtract * @throws ArithmeticException if numeric overflow occurs */ - public Temporal subtractFrom(Temporal temporal); + Temporal subtractFrom(Temporal temporal); } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalField.java --- a/src/share/classes/java/time/temporal/TemporalField.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/TemporalField.java Sat Apr 13 21:51:36 2013 +0100 @@ -62,8 +62,10 @@ package java.time.temporal; import java.time.DateTimeException; -import java.util.Comparator; +import java.time.format.ResolverStyle; +import java.util.Locale; import java.util.Map; +import java.util.Objects; /** * A field of date-time, such as month-of-year or hour-of-minute. @@ -88,7 +90,7 @@ * * @since 1.8 */ -public interface TemporalField extends Comparator { +public interface TemporalField { /** * Gets a descriptive name for the field. @@ -102,6 +104,21 @@ String getName(); /** + * Gets the display name for the field in the requested locale. + *

    + * If there is no display name for the locale the value of {@code getName} + * is returned. + * + * @param locale the locale to use, not null + * @return the display name for the locale or the value of {@code getName}, + * not null + */ + default String getDisplayName(Locale locale) { + Objects.requireNonNull(locale, "local"); + return getName(); + } + + /** * Gets the unit that the field is measured in. *

    * The unit of the field is the period that varies within the range. @@ -126,28 +143,6 @@ */ TemporalUnit getRangeUnit(); - //----------------------------------------------------------------------- - /** - * Compares the value of this field in two temporal objects. - *

    - * All fields implement {@link Comparator} on {@link TemporalAccessor}. - * This allows a list of date-times to be compared using the value of a field. - * For example, you could sort a list of arbitrary temporal objects by the value of - * the month-of-year field - {@code Collections.sort(list, MONTH_OF_YEAR)} - *

    - * The default implementation must behave equivalent to this code: - *

    -     *  return Long.compare(temporal1.getLong(this), temporal2.getLong(this));
    -     * 
    - * - * @param temporal1 the first temporal object to compare, not null - * @param temporal2 the second temporal object to compare, not null - * @throws DateTimeException if unable to obtain the value for this field - */ - public default int compare(TemporalAccessor temporal1, TemporalAccessor temporal2) { - return Long.compare(temporal1.getLong(this), temporal2.getLong(this)); - } - /** * Gets the range of valid values for the field. *

    @@ -165,6 +160,35 @@ //----------------------------------------------------------------------- /** + * Checks if this field represents a component of a date. + *

    + * A field is date-based if it can be derived from + * {@link ChronoField#EPOCH_DAY EPOCH_DAY}. + *

    + * The default implementation must return false. + * + * @return true if this field is a component of a date + */ + default boolean isDateBased() { + return false; + } + + /** + * Checks if this field represents a component of a time. + *

    + * A field is time-based if it can be derived from + * {@link ChronoField#NANO_OF_DAY NANO_OF_DAY}. + *

    + * The default implementation must return false. + * + * @return true if this field is a component of a time + */ + default boolean isTimeBased() { + return false; + } + + //----------------------------------------------------------------------- + /** * Checks if this field is supported by the temporal object. *

    * This determines whether the temporal accessor supports this field. @@ -213,11 +237,12 @@ *

    * Implementations should perform any queries or calculations using the fields * available in {@link ChronoField}. - * If the field is not supported a {@code DateTimeException} must be thrown. + * If the field is not supported an {@code UnsupportedTemporalTypeException} must be thrown. * * @param temporal the temporal object used to refine the result, not null * @return the range of valid values for this field, not null * @throws DateTimeException if the range for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported by the temporal */ ValueRange rangeRefinedBy(TemporalAccessor temporal); @@ -240,11 +265,12 @@ *

    * Implementations should perform any queries or calculations using the fields * available in {@link ChronoField}. - * If the field is not supported a {@code DateTimeException} must be thrown. + * If the field is not supported an {@code UnsupportedTemporalTypeException} must be thrown. * * @param temporal the temporal object to query, not null * @return the value of this field, not null * @throws DateTimeException if a value for the field cannot be obtained + * @throws UnsupportedTemporalTypeException if the field is not supported by the temporal * @throws ArithmeticException if numeric overflow occurs */ long getFrom(TemporalAccessor temporal); @@ -276,7 +302,7 @@ *

    * Implementations should perform any queries or calculations using the fields * available in {@link ChronoField}. - * If the field is not supported a {@code DateTimeException} must be thrown. + * If the field is not supported an {@code UnsupportedTemporalTypeException} must be thrown. *

    * Implementations must not alter the specified temporal object. * Instead, an adjusted copy of the original must be returned. @@ -287,6 +313,7 @@ * @param newValue the new value of the field * @return the adjusted temporal object, not null * @throws DateTimeException if the field cannot be set + * @throws UnsupportedTemporalTypeException if the field is not supported by the temporal * @throws ArithmeticException if numeric overflow occurs */ R adjustInto(R temporal, long newValue); @@ -314,17 +341,22 @@ * If the result is non-null, this field will be removed from the temporal. * This field should not be added to the result map. *

    + * The {@link ResolverStyle} should be used by implementations to determine + * how to perform the resolve. + *

    * The default implementation must return null. * * @param temporal the temporal to resolve, not null * @param value the value of this field + * @param resolverStyle the requested type of resolve, not null * @return a map of fields to update in the temporal, with a mapping to null * indicating a deletion. The whole map must be null if no resolving occurred * @throws DateTimeException if resolving results in an error. This must not be thrown * by querying a field on the temporal without first checking if it is supported * @throws ArithmeticException if numeric overflow occurs */ - public default Map resolve(TemporalAccessor temporal, long value) { + default Map resolve( + TemporalAccessor temporal, long value, ResolverStyle resolverStyle) { return null; } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalQueries.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/time/temporal/TemporalQueries.java Sat Apr 13 21:51:36 2013 +0100 @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.chrono.Chronology; + +/** + * Common implementations of {@code TemporalQuery}. + *

    + * This class provides common implementations of {@link TemporalQuery}. + * These are defined here as they must be constants, and the definition + * of lambdas does not guarantee that. By assigning them once here, + * they become 'normal' Java constants. + * + * @since 1.8 + */ +final class TemporalQueries { + // note that it is vital that each method supplies a constant, not a + // calculated value, as they will be checked for using == + // it is also vital that each constant is different (due to the == checking) + // as such, alterations to this code must be done with care + + /** + * Private constructor since this is a utility class. + */ + private TemporalQueries() { + } + + //----------------------------------------------------------------------- + /** + * A strict query for the {@code ZoneId}. + */ + static final TemporalQuery ZONE_ID = (temporal) -> { + return temporal.query(ZONE_ID); + }; + + /** + * A query for the {@code Chronology}. + */ + static final TemporalQuery CHRONO = (temporal) -> { + return temporal.query(CHRONO); + }; + + /** + * A query for the smallest supported unit. + */ + static final TemporalQuery PRECISION = (temporal) -> { + return temporal.query(PRECISION); + }; + + //----------------------------------------------------------------------- + /** + * A lenient query for the {@code ZoneId}, falling back to the {@code ZoneOffset}. + */ + static final TemporalQuery ZONE = (temporal) -> { + ZoneId zone = temporal.query(ZONE_ID); + return (zone != null ? zone : temporal.query(OFFSET)); + }; + + /** + * A query for {@code ZoneOffset} returning null if not found. + */ + static final TemporalQuery OFFSET = (temporal) -> { + if (temporal.isSupported(OFFSET_SECONDS)) { + return ZoneOffset.ofTotalSeconds(temporal.get(OFFSET_SECONDS)); + } + return null; + }; + + /** + * A query for {@code LocalDate} returning null if not found. + */ + static final TemporalQuery LOCAL_DATE = (temporal) -> { + if (temporal.isSupported(EPOCH_DAY)) { + return LocalDate.ofEpochDay(temporal.getLong(EPOCH_DAY)); + } + return null; + }; + + /** + * A query for {@code LocalTime} returning null if not found. + */ + static final TemporalQuery LOCAL_TIME = (temporal) -> { + if (temporal.isSupported(NANO_OF_DAY)) { + return LocalTime.ofNanoOfDay(temporal.getLong(NANO_OF_DAY)); + } + return null; + }; + +} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalQuery.java --- a/src/share/classes/java/time/temporal/TemporalQuery.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/TemporalQuery.java Sat Apr 13 21:51:36 2013 +0100 @@ -62,6 +62,11 @@ package java.time.temporal; import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.chrono.Chronology; /** * Strategy for querying a temporal object. @@ -89,8 +94,7 @@ *

    * The most common implementations are method references, such as * {@code LocalDate::from} and {@code ZoneId::from}. - * Further implementations are on {@link Queries}. - * Queries may also be defined by applications. + * Additional common implementations are provided on this interface as static methods. * *

    Specification for implementors

    * This interface places no restrictions on the mutability of implementations, @@ -129,7 +133,7 @@ *

    * The input temporal object may be in a calendar system other than ISO. * Implementations may choose to document compatibility with other calendar systems, - * or reject non-ISO temporal objects by {@link Queries#chronology() querying the chronology}. + * or reject non-ISO temporal objects by {@link TemporalQuery#chronology() querying the chronology}. *

    * This method may be called from multiple threads in parallel. * It must be thread-safe when invoked. @@ -141,4 +145,214 @@ */ R queryFrom(TemporalAccessor temporal); + //----------------------------------------------------------------------- + // special constants should be used to extract information from a TemporalAccessor + // that cannot be derived in other ways + // Javadoc added here, so as to pretend they are more normal than they really are + + /** + * A strict query for the {@code ZoneId}. + *

    + * This queries a {@code TemporalAccessor} for the zone. + * The zone is only returned if the date-time conceptually contains a {@code ZoneId}. + * It will not be returned if the date-time only conceptually has an {@code ZoneOffset}. + * Thus a {@link java.time.ZonedDateTime} will return the result of {@code getZone()}, + * but an {@link java.time.OffsetDateTime} will return null. + *

    + * In most cases, applications should use {@link #zone()} as this query is too strict. + *

    + * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
    + * {@code LocalDate} returns null
    + * {@code LocalTime} returns null
    + * {@code LocalDateTime} returns null
    + * {@code ZonedDateTime} returns the associated zone
    + * {@code OffsetTime} returns null
    + * {@code OffsetDateTime} returns null
    + * {@code ChronoLocalDate} returns null
    + * {@code ChronoLocalDateTime} returns null
    + * {@code ChronoZonedDateTime} returns the associated zone
    + * {@code Era} returns null
    + * {@code DayOfWeek} returns null
    + * {@code Month} returns null
    + * {@code Year} returns null
    + * {@code YearMonth} returns null
    + * {@code MonthDay} returns null
    + * {@code ZoneOffset} returns null
    + * {@code Instant} returns null
    + * + * @return a query that can obtain the zone ID of a temporal, not null + */ + static TemporalQuery zoneId() { + return TemporalQueries.ZONE_ID; + } + + /** + * A query for the {@code Chronology}. + *

    + * This queries a {@code TemporalAccessor} for the chronology. + * If the target {@code TemporalAccessor} represents a date, or part of a date, + * then it should return the chronology that the date is expressed in. + * As a result of this definition, objects only representing time, such as + * {@code LocalTime}, will return null. + *

    + * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
    + * {@code LocalDate} returns {@code IsoChronology.INSTANCE}
    + * {@code LocalTime} returns null (does not represent a date)
    + * {@code LocalDateTime} returns {@code IsoChronology.INSTANCE}
    + * {@code ZonedDateTime} returns {@code IsoChronology.INSTANCE}
    + * {@code OffsetTime} returns null (does not represent a date)
    + * {@code OffsetDateTime} returns {@code IsoChronology.INSTANCE}
    + * {@code ChronoLocalDate} returns the associated chronology
    + * {@code ChronoLocalDateTime} returns the associated chronology
    + * {@code ChronoZonedDateTime} returns the associated chronology
    + * {@code Era} returns the associated chronology
    + * {@code DayOfWeek} returns null (shared across chronologies)
    + * {@code Month} returns {@code IsoChronology.INSTANCE}
    + * {@code Year} returns {@code IsoChronology.INSTANCE}
    + * {@code YearMonth} returns {@code IsoChronology.INSTANCE}
    + * {@code MonthDay} returns null {@code IsoChronology.INSTANCE}
    + * {@code ZoneOffset} returns null (does not represent a date)
    + * {@code Instant} returns null (does not represent a date)
    + *

    + * The method {@link java.time.chrono.Chronology#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code Chronology::from}. + * That method is equivalent to this query, except that it throws an + * exception if a chronology cannot be obtained. + * + * @return a query that can obtain the chronology of a temporal, not null + */ + static TemporalQuery chronology() { + return TemporalQueries.CHRONO; + } + + /** + * A query for the smallest supported unit. + *

    + * This queries a {@code TemporalAccessor} for the time precision. + * If the target {@code TemporalAccessor} represents a consistent or complete date-time, + * date or time then this must return the smallest precision actually supported. + * Note that fields such as {@code NANO_OF_DAY} and {@code NANO_OF_SECOND} + * are defined to always return ignoring the precision, thus this is the only + * way to find the actual smallest supported unit. + * For example, were {@code GregorianCalendar} to implement {@code TemporalAccessor} + * it would return a precision of {@code MILLIS}. + *

    + * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
    + * {@code LocalDate} returns {@code DAYS}
    + * {@code LocalTime} returns {@code NANOS}
    + * {@code LocalDateTime} returns {@code NANOS}
    + * {@code ZonedDateTime} returns {@code NANOS}
    + * {@code OffsetTime} returns {@code NANOS}
    + * {@code OffsetDateTime} returns {@code NANOS}
    + * {@code ChronoLocalDate} returns {@code DAYS}
    + * {@code ChronoLocalDateTime} returns {@code NANOS}
    + * {@code ChronoZonedDateTime} returns {@code NANOS}
    + * {@code Era} returns {@code ERAS}
    + * {@code DayOfWeek} returns {@code DAYS}
    + * {@code Month} returns {@code MONTHS}
    + * {@code Year} returns {@code YEARS}
    + * {@code YearMonth} returns {@code MONTHS}
    + * {@code MonthDay} returns null (does not represent a complete date or time)
    + * {@code ZoneOffset} returns null (does not represent a date or time)
    + * {@code Instant} returns {@code NANOS}
    + * + * @return a query that can obtain the precision of a temporal, not null + */ + static TemporalQuery precision() { + return TemporalQueries.PRECISION; + } + + //----------------------------------------------------------------------- + // non-special constants are standard queries that derive information from other information + /** + * A lenient query for the {@code ZoneId}, falling back to the {@code ZoneOffset}. + *

    + * This queries a {@code TemporalAccessor} for the zone. + * It first tries to obtain the zone, using {@link #zoneId()}. + * If that is not found it tries to obtain the {@link #offset()}. + * Thus a {@link java.time.ZonedDateTime} will return the result of {@code getZone()}, + * while an {@link java.time.OffsetDateTime} will return the result of {@code getOffset()}. + *

    + * In most cases, applications should use this query rather than {@code #zoneId()}. + *

    + * The method {@link ZoneId#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code ZoneId::from}. + * That method is equivalent to this query, except that it throws an + * exception if a zone cannot be obtained. + * + * @return a query that can obtain the zone ID or offset of a temporal, not null + */ + static TemporalQuery zone() { + return TemporalQueries.ZONE; + } + + /** + * A query for {@code ZoneOffset} returning null if not found. + *

    + * This returns a {@code TemporalQuery} that can be used to query a temporal + * object for the offset. The query will return null if the temporal + * object cannot supply an offset. + *

    + * The query implementation examines the {@link ChronoField#OFFSET_SECONDS OFFSET_SECONDS} + * field and uses it to create a {@code ZoneOffset}. + *

    + * The method {@link java.time.ZoneOffset#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code ZoneOffset::from}. + * This query and {@code ZoneOffset::from} will return the same result if the + * temporal object contains an offset. If the temporal object does not contain + * an offset, then the method reference will throw an exception, whereas this + * query will return null. + * + * @return a query that can obtain the offset of a temporal, not null + */ + static TemporalQuery offset() { + return TemporalQueries.OFFSET; + } + + /** + * A query for {@code LocalDate} returning null if not found. + *

    + * This returns a {@code TemporalQuery} that can be used to query a temporal + * object for the local date. The query will return null if the temporal + * object cannot supply a local date. + *

    + * The query implementation examines the {@link ChronoField#EPOCH_DAY EPOCH_DAY} + * field and uses it to create a {@code LocalDate}. + *

    + * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code LocalDate::from}. + * This query and {@code LocalDate::from} will return the same result if the + * temporal object contains a date. If the temporal object does not contain + * a date, then the method reference will throw an exception, whereas this + * query will return null. + * + * @return a query that can obtain the date of a temporal, not null + */ + static TemporalQuery localDate() { + return TemporalQueries.LOCAL_DATE; + } + + /** + * A query for {@code LocalTime} returning null if not found. + *

    + * This returns a {@code TemporalQuery} that can be used to query a temporal + * object for the local time. The query will return null if the temporal + * object cannot supply a local time. + *

    + * The query implementation examines the {@link ChronoField#NANO_OF_DAY NANO_OF_DAY} + * field and uses it to create a {@code LocalTime}. + *

    + * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code LocalTime::from}. + * This query and {@code LocalTime::from} will return the same result if the + * temporal object contains a time. If the temporal object does not contain + * a time, then the method reference will throw an exception, whereas this + * query will return null. + * + * @return a query that can obtain the time of a temporal, not null + */ + static TemporalQuery localTime() { + return TemporalQueries.LOCAL_TIME; + } + } diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/TemporalUnit.java --- a/src/share/classes/java/time/temporal/TemporalUnit.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/TemporalUnit.java Sat Apr 13 21:51:36 2013 +0100 @@ -143,10 +143,12 @@ * @param temporal the temporal object to check, not null * @return true if the unit is supported */ - public default boolean isSupportedBy(Temporal temporal) { + default boolean isSupportedBy(Temporal temporal) { try { temporal.plus(1, this); return true; + } catch (UnsupportedTemporalTypeException ex) { + return false; } catch (RuntimeException ex) { try { temporal.plus(-1, this); @@ -178,7 +180,7 @@ *

    * Implementations should perform any queries or calculations using the units * available in {@link ChronoUnit} or the fields available in {@link ChronoField}. - * If the unit is not supported a {@code DateTimeException} must be thrown. + * If the unit is not supported an {@code UnsupportedTemporalTypeException} must be thrown. *

    * Implementations must not alter the specified temporal object. * Instead, an adjusted copy of the original must be returned. @@ -189,6 +191,7 @@ * @param amount the amount of this unit to add, positive or negative * @return the adjusted temporal object, not null * @throws DateTimeException if the period cannot be added + * @throws UnsupportedTemporalTypeException if the unit is not supported by the temporal */ R addTo(R temporal, long amount); @@ -214,8 +217,8 @@ * The second is to use {@link Temporal#periodUntil(Temporal, TemporalUnit)}: *

          *   // these two lines are equivalent
    -     *   temporal = thisUnit.between(start, end);
    -     *   temporal = start.periodUntil(end, thisUnit);
    +     *   between = thisUnit.between(start, end);
    +     *   between = start.periodUntil(end, thisUnit);
          * 
    * The choice should be made based on which makes the code more readable. *

    @@ -229,14 +232,15 @@ *

    * Implementations should perform any queries or calculations using the units * available in {@link ChronoUnit} or the fields available in {@link ChronoField}. - * If the unit is not supported a {@code DateTimeException} must be thrown. + * If the unit is not supported an {@code UnsupportedTemporalTypeException} must be thrown. * Implementations must not alter the specified temporal objects. * * @param temporal1 the base temporal object, not null * @param temporal2 the other temporal object, not null - * @return the period between datetime1 and datetime2 in terms of this unit; - * positive if datetime2 is later than datetime1, negative if earlier + * @return the period between temporal1 and temporal2 in terms of this unit; + * positive if temporal2 is later than temporal1, negative if earlier * @throws DateTimeException if the period cannot be calculated + * @throws UnsupportedTemporalTypeException if the unit is not supported by the temporal * @throws ArithmeticException if numeric overflow occurs */ long between(Temporal temporal1, Temporal temporal2); diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/UnsupportedTemporalTypeException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/java/time/temporal/UnsupportedTemporalTypeException.java Sat Apr 13 21:51:36 2013 +0100 @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Copyright (c) 2013, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package java.time.temporal; + +import java.time.DateTimeException; + +/** + * UnsupportedTemporalTypeException indicates that a ChronoField or ChronoUnit is + * not supported for a Temporal class. + * + *

    Specification for implementors

    + * This class is intended for use in a single thread. + * + * @since 1.8 + */ +public class UnsupportedTemporalTypeException extends DateTimeException { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -6158898438688206006L; + + /** + * Constructs a new UnsupportedTemporalTypeException with the specified message. + * + * @param message the message to use for this exception, may be null + */ + public UnsupportedTemporalTypeException(String message) { + super(message); + } + + /** + * Constructs a new UnsupportedTemporalTypeException with the specified message and cause. + * + * @param message the message to use for this exception, may be null + * @param cause the cause of the exception, may be null + */ + public UnsupportedTemporalTypeException(String message, Throwable cause) { + super(message, cause); + } + +} diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/ValueRange.java --- a/src/share/classes/java/time/temporal/ValueRange.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/ValueRange.java Sat Apr 13 21:51:36 2013 +0100 @@ -305,11 +305,7 @@ */ public long checkValidValue(long value, TemporalField field) { if (isValidValue(value) == false) { - if (field != null) { - throw new DateTimeException("Invalid value for " + field.getName() + " (valid values " + this + "): " + value); - } else { - throw new DateTimeException("Invalid value (valid values " + this + "): " + value); - } + throw new DateTimeException(genInvalidFieldMessage(field, value)); } return value; } @@ -328,11 +324,19 @@ */ public int checkValidIntValue(long value, TemporalField field) { if (isValidIntValue(value) == false) { - throw new DateTimeException("Invalid int value for " + field.getName() + ": " + value); + throw new DateTimeException(genInvalidFieldMessage(field, value)); } return (int) value; } + private String genInvalidFieldMessage(TemporalField field, long value) { + if (field != null) { + return "Invalid value for " + field.getName() + " (valid values " + this + "): " + value; + } else { + return "Invalid value (valid values " + this + "): " + value; + } + } + //----------------------------------------------------------------------- /** * Checks if this range is equal to another range. diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/WeekFields.java --- a/src/share/classes/java/time/temporal/WeekFields.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/WeekFields.java Sat Apr 13 21:51:36 2013 +0100 @@ -68,6 +68,7 @@ import static java.time.temporal.ChronoField.MONTH_OF_YEAR; import static java.time.temporal.ChronoField.YEAR; import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.FOREVER; import static java.time.temporal.ChronoUnit.MONTHS; import static java.time.temporal.ChronoUnit.WEEKS; import static java.time.temporal.ChronoUnit.YEARS; @@ -77,14 +78,18 @@ import java.time.DayOfWeek; import java.time.chrono.ChronoLocalDate; import java.time.chrono.Chronology; +import java.time.format.ResolverStyle; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.ResourceBundle; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import sun.util.locale.provider.CalendarDataUtility; +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.LocaleResources; /** * Localized definitions of the day-of-week, week-of-month and week-of-year fields. @@ -93,8 +98,9 @@ * other aspects of a week. This class represents the definition of the week, for the * purpose of providing {@link TemporalField} instances. *

    - * WeekFields provides three fields, - * {@link #dayOfWeek()}, {@link #weekOfMonth()}, and {@link #weekOfYear()} + * WeekFields provides five fields, + * {@link #dayOfWeek()}, {@link #weekOfMonth()}, {@link #weekOfYear()}, + * {@link #weekOfWeekBasedYear()}, and {@link #weekBasedYear()} * that provide access to the values from any {@linkplain Temporal temporal object}. *

    * The computations for day-of-week, week-of-month, and week-of-year are based @@ -110,7 +116,7 @@ *

  • The first day-of-week. * For example, the ISO-8601 standard considers Monday to be the first day-of-week. *
  • The minimal number of days in the first week. - * For example, the ISO-08601 standard counts the first week as needing at least 4 days. + * For example, the ISO-8601 standard counts the first week as needing at least 4 days. *

    * Together these two values allow a year or month to be divided into weeks. *

    @@ -134,14 +140,37 @@ * 2009-01-05Monday * Week 2 of January 2009Week 1 of January 2009 * - *

    + * *

    Week of Year

    * One field is used: week-of-year. * The calculation ensures that weeks never overlap a year boundary. * The year is divided into periods where each period starts on the defined first day-of-week. * The earliest period is referred to as week 0 if it has less than the minimal number of days * and week 1 if it has at least the minimal number of days. - *

    + * + *

    Week Based Year

    + * Two fields are used for week-based-year, one for the + * {@link #weekOfWeekBasedYear() week-of-week-based-year} and one for + * {@link #weekBasedYear() week-based-year}. In a week-based-year, each week + * belongs to only a single year. Week 1 of a year is the first week that + * starts on the first day-of-week and has at least the minimum number of days. + * The first and last weeks of a year may contain days from the + * previous calendar year or next calendar year respectively. + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Examples of WeekFields for week-based-year
    DateDay-of-weekFirst day: Monday
    Minimal days: 4
    First day: Monday
    Minimal days: 5
    2008-12-31WednesdayWeek 1 of 2009Week 53 of 2008
    2009-01-01ThursdayWeek 1 of 2009Week 53 of 2008
    2009-01-04SundayWeek 1 of 2009Week 53 of 2008
    2009-01-05MondayWeek 2 of 2009Week 1 of 2009
    + *

    Specification for implementors

    * This class is immutable and thread-safe. * * @since 1.8 @@ -171,19 +200,41 @@ * Note that the first week may start in the previous calendar year. * Note also that the first few days of a calendar year may be in the * week-based-year corresponding to the previous calendar year. + *

    + * This field is an immutable and thread-safe singleton. */ public static final WeekFields ISO = new WeekFields(DayOfWeek.MONDAY, 4); /** - * The common definition of a week that starts on Sunday. + * The common definition of a week that starts on Sunday and the first week + * has a minimum of 1 day. *

    * Defined as starting on Sunday and with a minimum of 1 day in the month. * This week definition is in use in the US and other European countries. - * + *

    + * This field is an immutable and thread-safe singleton. */ public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1); /** + * The unit that represents week-based-years for the purpose of addition and subtraction. + *

    + * This allows a number of week-based-years to be added to, or subtracted from, a date. + * The unit is equal to either 52 or 53 weeks. + * The estimated duration of a week-based-year is the same as that of a standard ISO + * year at {@code 365.2425 Days}. + *

    + * The rules for addition add the number of week-based-years to the existing value + * for the week-based-year field retaining the week-of-week-based-year + * and day-of-week, unless the week number it too large for the target year. + * In that case, the week is set to the last week of the year + * with the same day-of-week. + *

    + * This field is an immutable and thread-safe singleton. + */ + public static final TemporalUnit WEEK_BASED_YEARS = IsoFields.WEEK_BASED_YEARS; + + /** * Serialization version. */ private static final long serialVersionUID = -1177360819670808121L; @@ -213,6 +264,24 @@ private transient final TemporalField weekOfYear = ComputedDayOfField.ofWeekOfYearField(this); /** + * The field that represents the week-of-week-based-year. + *

    + * This field allows the week of the week-based-year value to be queried and set. + *

    + * This unit is an immutable and thread-safe singleton. + */ + private transient final TemporalField weekOfWeekBasedYear = ComputedDayOfField.ofWeekOfWeekBasedYearField(this); + + /** + * The field that represents the week-based-year. + *

    + * This field allows the week-based-year value to be queried and set. + *

    + * This unit is an immutable and thread-safe singleton. + */ + private transient final TemporalField weekBasedYear = ComputedDayOfField.ofWeekBasedYearField(this); + + /** * Obtains an instance of {@code WeekFields} appropriate for a locale. *

    * This will look up appropriate values from the provider of localization data. @@ -341,7 +410,7 @@ * Returns a field to access the week of month, * computed based on this WeekFields. *

    - * This represents concept of the count of weeks within the month where weeks + * This represents the concept of the count of weeks within the month where weeks * start on a fixed day-of-week, such as Monday. * This field is typically used with {@link WeekFields#dayOfWeek()}. *

    @@ -367,12 +436,12 @@ * Returns a field to access the week of year, * computed based on this WeekFields. *

    - * This represents concept of the count of weeks within the year where weeks + * This represents the concept of the count of weeks within the year where weeks * start on a fixed day-of-week, such as Monday. * This field is typically used with {@link WeekFields#dayOfWeek()}. *

    * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} - * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month. + * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. * Thus, week one may start up to {@code minDays} days before the start of the year. * If the first week starts after the start of the year then the period before is week zero (0). *

    @@ -390,7 +459,59 @@ } /** - * Checks if these rules are equal to the specified rules. + * Returns a field to access the week of a week-based-year, + * computed based on this WeekFields. + *

    + * This represents the concept of the count of weeks within the year where weeks + * start on a fixed day-of-week, such as Monday and each week belongs to exactly one year. + * This field is typically used with {@link WeekFields#dayOfWeek()} and + * {@link WeekFields#weekBasedYear()}. + *

    + * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} + * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. + * If the first week starts after the start of the year then the period before + * is in the last week of the previous year. + *

    + * For example:
    + * - if the 1st day of the year is a Monday, week one starts on the 1st
    + * - if the 2nd day of the year is a Monday, week one starts on the 2nd and + * the 1st is in the last week of the previous year
    + * - if the 4th day of the year is a Monday, week one starts on the 4th and + * the 1st to 3rd is in the last week of the previous year
    + * - if the 5th day of the year is a Monday, week two starts on the 5th and + * the 1st to 4th is in week one
    + *

    + * This field can be used with any calendar system. + * @return a TemporalField to access the week of week-based-year, not null + */ + public TemporalField weekOfWeekBasedYear() { + return weekOfWeekBasedYear; + } + + /** + * Returns a field to access the year of a week-based-year, + * computed based on this WeekFields. + *

    + * This represents the concept of the year where weeks start on a fixed day-of-week, + * such as Monday and each week belongs to exactly one year. + * This field is typically used with {@link WeekFields#dayOfWeek()} and + * {@link WeekFields#weekOfWeekBasedYear()}. + *

    + * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} + * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the year. + * Thus, week one may start before the start of the year. + * If the first week starts after the start of the year then the period before + * is in the last week of the previous year. + *

    + * This field can be used with any calendar system. + * @return a TemporalField to access the year of week-based-year, not null + */ + public TemporalField weekBasedYear() { + return weekBasedYear; + } + + /** + * Checks if this WeekFields is equal to the specified object. *

    * The comparison is based on the entire state of the rules, which is * the first day-of-week and minimal days. @@ -469,6 +590,49 @@ static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) { return new ComputedDayOfField("WeekOfYear", weekDef, WEEKS, YEARS, WEEK_OF_YEAR_RANGE); } + + /** + * Returns a field to access the week of week-based-year, + * computed based on a WeekFields. + * @see WeekFields#weekOfWeekBasedYear() + */ + static ComputedDayOfField ofWeekOfWeekBasedYearField(WeekFields weekDef) { + return new ComputedDayOfField("WeekOfWeekBasedYear", weekDef, WEEKS, IsoFields.WEEK_BASED_YEARS, WEEK_OF_YEAR_RANGE); + } + + /** + * Returns a field to access the week of week-based-year, + * computed based on a WeekFields. + * @see WeekFields#weekBasedYear() + */ + static ComputedDayOfField ofWeekBasedYearField(WeekFields weekDef) { + return new ComputedDayOfField("WeekBasedYear", weekDef, IsoFields.WEEK_BASED_YEARS, FOREVER, ChronoField.YEAR.range()); + } + + /** + * Return a new week-based-year date of the Chronology, year, week-of-year, + * and dow of week. + * @param chrono The chronology of the new date + * @param yowby the year of the week-based-year + * @param wowby the week of the week-based-year + * @param dow the day of the week + * @return a ChronoLocalDate for the requested year, week of year, and day of week + */ + private ChronoLocalDate ofWeekBasedYear(Chronology chrono, + int yowby, int wowby, int dow) { + ChronoLocalDate date = chrono.date(yowby, 1, 1); + int ldow = localizedDayOfWeek(date); + int offset = startOfWeekOffset(1, ldow); + + // Clamp the week of year to keep it in the same year + int yearLen = date.lengthOfYear(); + int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); + wowby = Math.min(wowby, newYearWeek - 1); + + int days = -offset + (dow - 1) + (wowby - 1) * 7; + return date.plus(days, DAYS); + } + private final String name; private final WeekFields weekDef; private final TemporalUnit baseUnit; @@ -489,44 +653,109 @@ @Override public long getFrom(TemporalAccessor temporal) { - // Offset the ISO DOW by the start of this week - int sow = weekDef.getFirstDayOfWeek().getValue(); - int dow = localizedDayOfWeek(temporal, sow); - if (rangeUnit == WEEKS) { // day-of-week - return dow; + return localizedDayOfWeek(temporal); } else if (rangeUnit == MONTHS) { // week-of-month - return localizedWeekOfMonth(temporal, dow); + return localizedWeekOfMonth(temporal); } else if (rangeUnit == YEARS) { // week-of-year - return localizedWeekOfYear(temporal, dow); + return localizedWeekOfYear(temporal); + } else if (rangeUnit == WEEK_BASED_YEARS) { + return localizedWeekOfWeekBasedYear(temporal); + } else if (rangeUnit == FOREVER) { + return localizedWeekBasedYear(temporal); } else { - throw new IllegalStateException("unreachable"); + throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this); } } - private int localizedDayOfWeek(TemporalAccessor temporal, int sow) { + private int localizedDayOfWeek(TemporalAccessor temporal) { + int sow = weekDef.getFirstDayOfWeek().getValue(); int isoDow = temporal.get(DAY_OF_WEEK); return Math.floorMod(isoDow - sow, 7) + 1; } - private long localizedWeekOfMonth(TemporalAccessor temporal, int dow) { + private long localizedWeekOfMonth(TemporalAccessor temporal) { + int dow = localizedDayOfWeek(temporal); int dom = temporal.get(DAY_OF_MONTH); int offset = startOfWeekOffset(dom, dow); return computeWeek(offset, dom); } - private long localizedWeekOfYear(TemporalAccessor temporal, int dow) { + private long localizedWeekOfYear(TemporalAccessor temporal) { + int dow = localizedDayOfWeek(temporal); int doy = temporal.get(DAY_OF_YEAR); int offset = startOfWeekOffset(doy, dow); return computeWeek(offset, doy); } /** + * Returns the year of week-based-year for the temporal. + * The year can be the previous year, the current year, or the next year. + * @param temporal a date of any chronology, not null + * @return the year of week-based-year for the date + */ + private int localizedWeekBasedYear(TemporalAccessor temporal) { + int dow = localizedDayOfWeek(temporal); + int year = temporal.get(YEAR); + int doy = temporal.get(DAY_OF_YEAR); + int offset = startOfWeekOffset(doy, dow); + int week = computeWeek(offset, doy); + if (week == 0) { + // Day is in end of week of previous year; return the previous year + return year - 1; + } else { + // If getting close to end of year, use higher precision logic + // Check if date of year is in partial week associated with next year + ValueRange dayRange = temporal.range(DAY_OF_YEAR); + int yearLen = (int)dayRange.getMaximum(); + int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); + if (week >= newYearWeek) { + return year + 1; + } + } + return year; + } + + /** + * Returns the week of week-based-year for the temporal. + * The week can be part of the previous year, the current year, + * or the next year depending on the week start and minimum number + * of days. + * @param temporal a date of any chronology + * @return the week of the year + * @see #localizedWeekBasedYear(java.time.temporal.TemporalAccessor) + */ + private int localizedWeekOfWeekBasedYear(TemporalAccessor temporal) { + int dow = localizedDayOfWeek(temporal); + int doy = temporal.get(DAY_OF_YEAR); + int offset = startOfWeekOffset(doy, dow); + int week = computeWeek(offset, doy); + if (week == 0) { + // Day is in end of week of previous year + // Recompute from the last day of the previous year + ChronoLocalDate date = Chronology.from(temporal).date(temporal); + date = date.minus(doy, DAYS); // Back down into previous year + return localizedWeekOfWeekBasedYear(date); + } else if (week > 50) { + // If getting close to end of year, use higher precision logic + // Check if date of year is in partial week associated with next year + ValueRange dayRange = temporal.range(DAY_OF_YEAR); + int yearLen = (int)dayRange.getMaximum(); + int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); + if (week >= newYearWeek) { + // Overlaps with week of following year; reduce to week in following year + week = week - newYearWeek + 1; + } + } + return week; + } + + /** * Returns an offset to align week start with a day of month or day of year. * - * @param day the day; 1 through infinity - * @param dow the day of the week of that day; 1 through 7 - * @return an offset in days to align a day with the start of the first 'full' week + * @param day the day; 1 through infinity + * @param dow the day of the week of that day; 1 through 7 + * @return an offset in days to align a day with the start of the first 'full' week */ private int startOfWeekOffset(int day, int dow) { // offset of first day corresponding to the day of week in first 7 days (zero origin) @@ -560,54 +789,81 @@ if (newVal == currentVal) { return temporal; } - // Compute the difference and add that using the base using of the field - return (R) temporal.plus(newVal - currentVal, baseUnit); + + if (rangeUnit == FOREVER) { // replace year of WeekBasedYear + // Create a new date object with the same chronology, + // the desired year and the same week and dow. + int idow = temporal.get(weekDef.dayOfWeek); + int wowby = temporal.get(weekDef.weekOfWeekBasedYear); + return (R) ofWeekBasedYear(Chronology.from(temporal), (int)newValue, wowby, idow); + } else { + // Compute the difference and add that using the base unit of the field + return (R) temporal.plus(newVal - currentVal, baseUnit); + } } @Override - public Map resolve(TemporalAccessor temporal, long value) { + public Map resolve(TemporalAccessor temporal, long value, ResolverStyle resolverStyle) { int newValue = range.checkValidIntValue(value, this); int sow = weekDef.getFirstDayOfWeek().getValue(); if (rangeUnit == WEEKS) { // day-of-week int isoDow = Math.floorMod((sow - 1) + (newValue - 1), 7) + 1; return Collections.singletonMap(DAY_OF_WEEK, (long) isoDow); } - if ((temporal.isSupported(YEAR) && temporal.isSupported(DAY_OF_WEEK)) == false) { + if (temporal.isSupported(DAY_OF_WEEK) == false) { return null; } - int dow = localizedDayOfWeek(temporal, sow); - int year = temporal.get(YEAR); Chronology chrono = Chronology.from(temporal); // defaults to ISO - if (rangeUnit == MONTHS) { // week-of-month - if (temporal.isSupported(MONTH_OF_YEAR) == false) { - return null; + int dow = localizedDayOfWeek(temporal); + if (temporal.isSupported(YEAR)) { + int year = temporal.get(YEAR); + if (rangeUnit == MONTHS) { // week-of-month + if (temporal.isSupported(MONTH_OF_YEAR) == false) { + return null; + } + int month = temporal.get(ChronoField.MONTH_OF_YEAR); + @SuppressWarnings("rawtypes") + ChronoLocalDate date = chrono.date(year, month, 1); + int dateDow = localizedDayOfWeek(date); + long weeks = newValue - localizedWeekOfMonth(date); + int days = dow - dateDow; + date = date.plus(weeks * 7 + days, DAYS); + Map result = new HashMap<>(4, 1.0f); + result.put(EPOCH_DAY, date.toEpochDay()); + result.put(YEAR, null); + result.put(MONTH_OF_YEAR, null); + result.put(DAY_OF_WEEK, null); + return result; + } else if (rangeUnit == YEARS) { // week-of-year + @SuppressWarnings("rawtypes") + ChronoLocalDate date = chrono.date(year, 1, 1); + int dateDow = localizedDayOfWeek(date); + long weeks = newValue - localizedWeekOfYear(date); + int days = dow - dateDow; + date = date.plus(weeks * 7 + days, DAYS); + Map result = new HashMap<>(4, 1.0f); + result.put(EPOCH_DAY, date.toEpochDay()); + result.put(YEAR, null); + result.put(DAY_OF_WEEK, null); + return result; } - int month = temporal.get(ChronoField.MONTH_OF_YEAR); - ChronoLocalDate date = chrono.date(year, month, 1); - int dateDow = localizedDayOfWeek(date, sow); - long weeks = newValue - localizedWeekOfMonth(date, dateDow); - int days = dow - dateDow; - date = date.plus(weeks * 7 + days, DAYS); - Map result = new HashMap<>(4, 1.0f); - result.put(EPOCH_DAY, date.toEpochDay()); - result.put(YEAR, null); - result.put(MONTH_OF_YEAR, null); - result.put(DAY_OF_WEEK, null); - return result; - } else if (rangeUnit == YEARS) { // week-of-year - ChronoLocalDate date = chrono.date(year, 1, 1); - int dateDow = localizedDayOfWeek(date, sow); - long weeks = newValue - localizedWeekOfYear(date, dateDow); - int days = dow - dateDow; - date = date.plus(weeks * 7 + days, DAYS); - Map result = new HashMap<>(4, 1.0f); - result.put(EPOCH_DAY, date.toEpochDay()); - result.put(YEAR, null); - result.put(DAY_OF_WEEK, null); - return result; - } else { - throw new IllegalStateException("unreachable"); + } else if (rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) { + if (temporal.isSupported(weekDef.weekBasedYear) && + temporal.isSupported(weekDef.weekOfWeekBasedYear)) { + // week-of-week-based-year and year-of-week-based-year + int yowby = temporal.get(weekDef.weekBasedYear); + int wowby = temporal.get(weekDef.weekOfWeekBasedYear); + ChronoLocalDate date = ofWeekBasedYear(Chronology.from(temporal), yowby, wowby, dow); + + Map result = new HashMap<>(4, 1.0f); + result.put(EPOCH_DAY, date.toEpochDay()); + result.put(DAY_OF_WEEK, null); + result.put(weekDef.weekOfWeekBasedYear, null); + result.put(weekDef.weekBasedYear, null); + return result; + } } + return null; } //----------------------------------------------------------------------- @@ -617,6 +873,18 @@ } @Override + public String getDisplayName(Locale locale) { + Objects.requireNonNull(locale, "locale"); + if (rangeUnit == YEARS) { // only have values for week-of-year + LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() + .getLocaleResources(locale); + ResourceBundle rb = lr.getJavaTimeFormatData(); + return rb.containsKey("field.week") ? rb.getString("field.week") : getName(); + } + return getName(); + } + + @Override public TemporalUnit getBaseUnit() { return baseUnit; } @@ -627,6 +895,11 @@ } @Override + public boolean isDateBased() { + return true; + } + + @Override public ValueRange range() { return range; } @@ -641,6 +914,10 @@ return temporal.isSupported(DAY_OF_MONTH); } else if (rangeUnit == YEARS) { // week-of-year return temporal.isSupported(DAY_OF_YEAR); + } else if (rangeUnit == WEEK_BASED_YEARS) { + return temporal.isSupported(DAY_OF_YEAR); + } else if (rangeUnit == FOREVER) { + return temporal.isSupported(YEAR); } } return false; @@ -650,27 +927,68 @@ public ValueRange rangeRefinedBy(TemporalAccessor temporal) { if (rangeUnit == ChronoUnit.WEEKS) { // day-of-week return range; - } - - TemporalField field = null; - if (rangeUnit == MONTHS) { // week-of-month - field = DAY_OF_MONTH; + } else if (rangeUnit == MONTHS) { // week-of-month + return rangeByWeek(temporal, DAY_OF_MONTH); } else if (rangeUnit == YEARS) { // week-of-year - field = DAY_OF_YEAR; + return rangeByWeek(temporal, DAY_OF_YEAR); + } else if (rangeUnit == WEEK_BASED_YEARS) { + return rangeWeekOfWeekBasedYear(temporal); + } else if (rangeUnit == FOREVER) { + return YEAR.range(); } else { - throw new IllegalStateException("unreachable"); + throw new IllegalStateException("unreachable, rangeUnit: " + rangeUnit + ", this: " + this); } + } - // Offset the ISO DOW by the start of this week - int sow = weekDef.getFirstDayOfWeek().getValue(); - int dow = localizedDayOfWeek(temporal, sow); - + /** + * Map the field range to a week range + * @param temporal the temporal + * @param field the field to get the range of + * @return the ValueRange with the range adjusted to weeks. + */ + private ValueRange rangeByWeek(TemporalAccessor temporal, TemporalField field) { + int dow = localizedDayOfWeek(temporal); int offset = startOfWeekOffset(temporal.get(field), dow); ValueRange fieldRange = temporal.range(field); return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()), computeWeek(offset, (int) fieldRange.getMaximum())); } + /** + * Map the field range to a week range of a week year. + * @param temporal the temporal + * @param field the field to get the range of + * @return the ValueRange with the range adjusted to weeks. + */ + private ValueRange rangeWeekOfWeekBasedYear(TemporalAccessor temporal) { + if (!temporal.isSupported(DAY_OF_YEAR)) { + return WEEK_OF_YEAR_RANGE; + } + int dow = localizedDayOfWeek(temporal); + int doy = temporal.get(DAY_OF_YEAR); + int offset = startOfWeekOffset(doy, dow); + int week = computeWeek(offset, doy); + if (week == 0) { + // Day is in end of week of previous year + // Recompute from the last day of the previous year + ChronoLocalDate date = Chronology.from(temporal).date(temporal); + date = date.minus(doy + 7, DAYS); // Back down into previous year + return rangeWeekOfWeekBasedYear(date); + } + // Check if day of year is in partial week associated with next year + ValueRange dayRange = temporal.range(DAY_OF_YEAR); + int yearLen = (int)dayRange.getMaximum(); + int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek()); + + if (week >= newYearWeek) { + // Overlaps with weeks of following year; recompute from a week in following year + ChronoLocalDate date = Chronology.from(temporal).date(temporal); + date = date.plus(yearLen - doy + 1 + 7, ChronoUnit.DAYS); + return rangeWeekOfWeekBasedYear(date); + } + return ValueRange.of(1, newYearWeek-1); + } + //----------------------------------------------------------------------- @Override public String toString() { diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/temporal/package-info.java --- a/src/share/classes/java/time/temporal/package-info.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/temporal/package-info.java Sat Apr 13 21:51:36 2013 +0100 @@ -112,9 +112,9 @@ * such as the "last day of the month", or "next Wednesday". * These are modeled as functions that adjust a base date-time. * The functions implement {@link java.time.temporal.TemporalAdjuster} and operate on {@code Temporal}. - * A set of common functions are provided in {@link java.time.temporal.Adjusters}. + * A set of common functions are provided in {@code TemporalAdjuster}. * For example, to find the first occurrence of a day-of-week after a given date, use - * {@link java.time.temporal.Adjusters#next(DayOfWeek)}, such as + * {@link java.time.temporal.TemporalAdjuster#next(DayOfWeek)}, such as * {@code date.with(next(MONDAY))}. * Applications can also define adjusters by implementing {@code TemporalAdjuster}. *

    @@ -127,7 +127,7 @@ * The most common implementations of the query interface are method references. * The {@code from(TemporalAccessor)} methods on major classes can all be used, such as * {@code LocalDate::from} or {@code Month::from}. - * Further implementations are provided in {@link java.time.temporal.Queries}. + * Further implementations are provided in {@code TemporalQuery} as static methods. * Applications can also define queries by implementing {@code TemporalQuery}. *

    * diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/zone/TzdbZoneRulesProvider.java --- a/src/share/classes/java/time/zone/TzdbZoneRulesProvider.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/zone/TzdbZoneRulesProvider.java Sat Apr 13 21:51:36 2013 +0100 @@ -64,6 +64,7 @@ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.StreamCorruptedException; import java.util.Arrays; @@ -75,7 +76,6 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.zip.ZipFile; /** * Loads time-zone rules for 'TZDB'. @@ -106,10 +106,8 @@ public TzdbZoneRulesProvider() { try { String libDir = System.getProperty("java.home") + File.separator + "lib"; - File tzdbJar = new File(libDir, "tzdb.jar"); - try (ZipFile zf = new ZipFile(tzdbJar); - DataInputStream dis = new DataInputStream( - zf.getInputStream(zf.getEntry("TZDB.dat")))) { + try (DataInputStream dis = new DataInputStream( + new FileInputStream(new File(libDir, "tzdb.dat")))) { load(dis); } } catch (Exception ex) { diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/zone/ZoneOffsetTransition.java --- a/src/share/classes/java/time/zone/ZoneOffsetTransition.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/zone/ZoneOffsetTransition.java Sat Apr 13 21:51:36 2013 +0100 @@ -321,7 +321,7 @@ } /** - * Does this transition represent a gap in the local time-line. + * Does this transition represent an overlap in the local time-line. *

    * Overlaps occur where there are local date-times that exist twice. * An example would be when the offset changes from {@code +02:00} to {@code +01:00}. diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java --- a/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java Sat Apr 13 21:51:36 2013 +0100 @@ -61,8 +61,8 @@ */ package java.time.zone; -import static java.time.temporal.Adjusters.nextOrSame; -import static java.time.temporal.Adjusters.previousOrSame; +import static java.time.temporal.TemporalAdjuster.nextOrSame; +import static java.time.temporal.TemporalAdjuster.previousOrSame; import java.io.DataInput; import java.io.DataOutput; diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/time/zone/ZoneRulesProvider.java --- a/src/share/classes/java/time/zone/ZoneRulesProvider.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/time/zone/ZoneRulesProvider.java Sat Apr 13 21:51:36 2013 +0100 @@ -141,7 +141,7 @@ // if the property java.time.zone.DefaultZoneRulesProvider is // set then its value is the class name of the default provider final List loaded = new ArrayList<>(); - AccessController.doPrivileged(new PrivilegedAction() { + AccessController.doPrivileged(new PrivilegedAction() { public Object run() { String prop = System.getProperty("java.time.zone.DefaultZoneRulesProvider"); if (prop != null) { diff -r 5f46a666e06d -r 84df34924f67 src/share/classes/java/util/ArrayList.java --- a/src/share/classes/java/util/ArrayList.java Sat Apr 13 20:16:00 2013 +0100 +++ b/src/share/classes/java/util/ArrayList.java Sat Apr 13 21:51:36 2013 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -105,8 +105,20 @@ private static final long serialVersionUID = 8683452581122892189L; /** + * Default initial capacity. + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * Shared empty array instance used for empty instances. + */ + private static final Object[] EMPTY_ELEMENTDATA = {}; + + /** * The array buffer into which the elements of the ArrayList are stored. - * The capacity of the ArrayList is the length of this array buffer. + * The capacity of the ArrayList is the length of this array buffer. Any + * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to + * DEFAULT_CAPACITY when the first element is added. */ private transient Object[] elementData; @@ -136,7 +148,8 @@ * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { - this(10); + super(); + this.elementData = EMPTY_ELEMENTDATA; } /** @@ -162,8 +175,7 @@ */ public void trimToSize() { modCount++; - int oldCapacity = elementData.length; - if (size < oldCapacity) { + if (size < elementData.length) { elementData = Arrays.copyOf(elementData, size); } } @@ -176,12 +188,29 @@ * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { - if (minCapacity > 0) - ensureCapacityInternal(minCapacity); + int minExpand = (elementData != EMPTY_ELEMENTDATA) + // any size if real element table + ? 0 + // larger than default for empty table. It's already supposed to be + // at default size. + : DEFAULT_CAPACITY; + + if (minCapacity > minExpand) { + ensureExplicitCapacity(minCapacity); + } } private void ensureCapacityInternal(int minCapacity) { + if (elementData == EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); + } + + private void ensureExplicitCapacity(int minCapacity) { modCount++; + // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); @@ -450,7 +479,7 @@ if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); - elementData[--size] = null; // Let gc do its work + elementData[--size] = null; // clear to let GC do its work return oldValue; } @@ -495,7 +524,7 @@ if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); - elementData[--size] = null; // Let gc do its work + elementData[--size] = null; // clear to let GC do its work } /** @@ -505,7 +534,7 @@ public void clear() { modCount++; - // Let gc do its work + // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; @@ -586,10 +615,12 @@ System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); - // Let gc do its work + // clear to let GC do its work int newSize = size - (toIndex-fromIndex); - while (size != newSize) - elementData[--size] = null; + for (int i = newSize; i < size; i++) { + elementData[i] = null; + } + size = newSize; } /** @@ -677,6 +708,7 @@ w += size - r; } if (w != size) { + // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; @@ -701,17 +733,17 @@ int expectedModCount = modCount; s.defaultWriteObject(); - // Write out array length - s.writeInt(elementData.length); + // Write out size as capacity for behavioural compatibility with clone() + s.writeInt(size); // Write out all elements in the proper order. - for (int i=0; i