Is there an implemented way to convert ISO 8601 period to a translated string readable by humans in Java?
Asked Answered
B

2

7

Is there a way to convert a string given in ISO 8601 period format to a format readable by humans and also translated to specified language? Example:

  • P1W --en--> 1 Week
  • P2W --en--> 2 Weeks
  • P1W --pl--> 1 Tydzień
  • P2W --pl--> 2 Tygodnie

I am looking for an already implemented solution, without the necessity of defining periods translations.

Base answered 5/10, 2022 at 11:36 Comment(3)
There is joda.time.PeriodFormatter and java.time.Period suggests such a text. InterestingGrandmamma
I’m pretty sure the CLDR itself (the official repository for all localization) has tools for doing this. When I have a chance, I hope to provide an answer illustrating their use.Retroact
Although this question asks whether a library is available, it is not specifically asking for that, in my opinion. It is asking how to accomplish a particular task which doesn’t have a simple solution in Java SE—namely, localized formatting of Period instances. Voting to reopen.Retroact
R
3

The Unicode Common Locale Data Repository, commonly known as the CLDR, is an authoritative repository for localized information of all kinds, including translations.

Since Java 9, Java SE has used the CLDR for a lot of (if not all) localization functionality. But I am not aware of any localized formatting of java.time.Period instances using CLDR data files.

The CLDR has its own Java tools, but I experimented with the compiled .jar for hours and could not figure out how to use it. I couldn’t find documentation beyond the (very short) command line help. If it has an API for formatting periods, I would definitely favor using that.

But the CLDR data files are still the best place to get this information. (They can also be cloned from https://github.com/unicode-org/cldr) So, with that in mind, I wrote a (rather long) class to format Period instances using CLDR data files.

There are a few steps involved:

  • First, code must determine the language cardinality for a particular number. In English, there are only two choices: singular and plural. Other languages can be more complex. The common/supplemental/plurals.xml file in the CLDR contains all of this data for all locales; its format is documented on unicode.org.
  • Most localized data is in the common/main directory of the CLDR, as .xml files named by locale. The elements related to periods are in <ldml><units><unitLength type="long"><unit type="duration-week">, as well as type="duration-year", type="duration-month", and type="duration-day". Within the unit element are <unitPattern> elements, each with a count attribute referencing the cardinality described above.
  • Finally, the same locale-specific file in common/main contains <listPattern> elements which describe how to format a list of items, including <listPattern type="unit"> for assembling a list of period parts. The use of this data is described here.

Note that Period.parse accepts the ISO 8601 format, including weeks, but the Period class itself does not keep track of weeks, so if output with weeks is desired, code must divide the Period’s days by 7.

Here is what I came up with. The parsing is not robust and makes a lot of assumptions about correct CLDR data. There are probably ways to speed up the reading of the XML files, such as caching recently used ones, so consider this a proof of concept:

public class PeriodFormatter {
    public enum Count {
        EXACTLY0("0"),
        EXACTLY1("1"),
        ZERO,
        ONE,
        TWO,
        FEW,
        MANY,
        OTHER;

        private final String attributeValue;

        Count() {
            this.attributeValue = name().toLowerCase(Locale.US);
        }

        Count(String attributeValue) {
            this.attributeValue = attributeValue;
        }

        String attributeValue() {
            return attributeValue;
        }

        static Count forAttributeValue(String attrValue) {
            for (Count count : values()) {
                if (count.attributeValue.equals(attrValue)) {
                    return count;
                }
            }
            throw new IllegalArgumentException(
                "No Count with attribute value \"" + attrValue + "\"");
        }
    }

    private static class Range {
        final long start;
        final long end;

        Range(long value) {
            this(value, value);
        }

        Range(long start,
              long end) {
            this.start = start;
            this.end = end;
        }

        boolean contains(long value) {
            return value >= start && value <= end;
        }

        boolean contains(double value) {
            return value >= start && value <= end;
        }
    }

    private enum Operand {
        ABSOLUTE_VALUE("n"),
        INTEGER_PART("i"),
        FRACTIONAL_PRECISION("v"),
        FRACTIONAL_PRECISION_TRIMMED("w"),
        FRACTIONAL_PART("f"),
        FRACTIONAL_PART_TRIMMED("t"),
        EXPONENT_PART("c", "e");

        final String[] tokens;

        private Operand(String... tokens) {
            this.tokens = tokens;
        }

        static Operand forToken(String token) {
            for (Operand op : values()) {
                if (Arrays.asList(op.tokens).contains(token)) {
                    return op;
                }
            }
            throw new IllegalArgumentException(
                "No Operand for token \"" + token + "\"");
        }
    }

    private static class Expression {
        final Operand operand;
        final Long modValue;

        Expression(Operand operand,
                   Long modValue) {
            this.operand = operand;
            this.modValue = modValue;
        }

        double evaluate(BigDecimal value) {
            switch (operand) {
                case ABSOLUTE_VALUE:
                    return Math.abs(value.doubleValue());
                case INTEGER_PART:
                    return value.longValue();
                case FRACTIONAL_PRECISION:
                    return Math.max(value.scale(), 0);
                case FRACTIONAL_PRECISION_TRIMMED:
                    return Math.max(value.stripTrailingZeros().scale(), 0);
                case FRACTIONAL_PART:
                    BigDecimal frac = value.remainder(BigDecimal.ONE);
                    frac = frac.movePointRight(Math.max(0, frac.scale()));
                    return frac.doubleValue();
                case FRACTIONAL_PART_TRIMMED:
                    BigDecimal trimmed =
                        value.stripTrailingZeros().remainder(BigDecimal.ONE);
                    trimmed = trimmed.movePointRight(
                        Math.max(0, trimmed.scale()));
                    return trimmed.longValue();
                case EXPONENT_PART:
                    String expStr = String.format("%e", value);
                    return Long.parseLong(
                        expStr.substring(expStr.indexOf('e') + 1));
                default:
                    break;
            }
            throw new RuntimeException("Unknown operand " + operand);
        }
    }

    private static abstract class Relation {
        boolean negated;
        Expression expr;

        abstract boolean matches(BigDecimal value);

        final boolean matchIfIntegral(BigDecimal value,
                                      LongPredicate test) {
            double evaluatedValue = expr.evaluate(value);
            long rounded = Math.round(evaluatedValue);
            return Math.abs(evaluatedValue - rounded) < 0.000001
                && test.test(rounded);
        }
    }

    private static class IsRelation
    extends Relation {
        long value;

        @Override
        boolean matches(BigDecimal value) {
            return matchIfIntegral(value, n -> n == this.value);
        }
    }

    private static class InRelation
    extends Relation {
        final List<Range> ranges = new ArrayList<>();

        @Override
        boolean matches(BigDecimal value) {
            return ranges.stream().anyMatch(
                range -> matchIfIntegral(value, n -> range.contains(n)));
        }
    }

    private static class WithinRelation
    extends Relation {
        final List<Range> ranges = new ArrayList<>();

        @Override
        boolean matches(BigDecimal value) {
            return ranges.stream().anyMatch(r -> r.contains(value.longValue()));
        }
    }

    private static class Condition {
        final List<Relation> relations = new ArrayList<>();

        boolean matches(BigDecimal value) {
            return relations.stream().allMatch(r -> r.matches(value));
        }
    }

    private static class AndConditionSequence {
        final List<Condition> conditions = new ArrayList<>();

        boolean matches(BigDecimal value) {
            return conditions.stream().allMatch(c -> c.matches(value));
        }
    }

    private static class PluralRule {
        final Count count;
        final List<AndConditionSequence> conditions = new ArrayList<>();

        PluralRule(String countSpec,
                   String ruleSpec) {

            this.count = Count.forAttributeValue(countSpec);

            Scanner scanner = new Scanner(ruleSpec);

            AndConditionSequence andSequence = new AndConditionSequence();
            Condition condition = new Condition();
            andSequence.conditions.add(condition);
            this.conditions.add(andSequence);

            while (true) {
                String token = scanner.findWithinHorizon("\\S", 0);
                if (token.equals("@")) {
                    // Ignore samples.
                    break;
                }

                Operand operand = Operand.forToken(token);
                Long modValue = null;

                token = scanner.findWithinHorizon(
                    "mod|%|is|in|within|!?=|not", 0);
                if (token.equals("mod") || token.equals("%")) {
                    modValue = Long.valueOf(
                        scanner.findWithinHorizon("\\d+", 0));
                    token = scanner.findWithinHorizon(
                        "is|in|within|!?=|not", 0);
                }

                Relation relation;

                boolean negated = false;
                if (token.equals("not")) {
                    token = scanner.findWithinHorizon("in|within", 0);
                    if (token.equals("within")) {
                        WithinRelation within = new WithinRelation();
                        relation = within;
                        within.negated = true;
                        parseRanges(within.ranges, scanner);
                    } else {
                        InRelation in = new InRelation();
                        relation = in;
                        in.negated = true;
                        parseRanges(in.ranges, scanner);
                    }
                    relation.negated = true;
                } else if (token.equals("is")) {
                    IsRelation is = new IsRelation();
                    relation = is;
                    token = scanner.findWithinHorizon("not|\\d+", 0);
                    if (token.equals("not")) {
                        is.negated = true;
                        token = scanner.findWithinHorizon("\\d+", 0);
                    }
                    is.value = Long.valueOf(token);
                } else if (token.endsWith("=")) {
                    InRelation in = new InRelation();
                    relation = in;
                    in.negated = token.startsWith("!");
                    parseRanges(in.ranges, scanner);
                } else {
                    throw new RuntimeException(
                        "Unexpected token '" + token + "'");
                }

                relation.expr = new Expression(operand, modValue);
                condition.relations.add(relation);

                if (!scanner.hasNext("and|or")) {
                    break;
                }

                token = scanner.next();
                if (token.equals("and")) {
                    condition = new Condition();
                    andSequence.conditions.add(condition);
                } else {
                    andSequence = new AndConditionSequence();
                    this.conditions.add(andSequence);
                }
            }
        }

        static void parseRanges(Collection<Range> ranges,
                                Scanner scanner) {

            boolean first = true;
            while (true) {
                if (!first) {
                    if (!scanner.hasNext(",.*")) {
                         break;
                    }
                    scanner.findWithinHorizon(",", 0);
                }

                String token =
                    scanner.findWithinHorizon("\\d+(?:\\.\\.\\d+)?", 0);

                int period = token.indexOf('.');
                if (period > 0) {
                    long start = Long.parseLong(token.substring(0, period));
                    long end = Long.parseLong(token.substring(period + 2));
                    ranges.add(new Range(start, end));
                } else {
                    long value = Long.parseLong(token);
                    ranges.add(new Range(value));
                }

                first = false;
            }
        }

        boolean matches(BigDecimal value) {
            return conditions.stream().anyMatch(c -> c.matches(value));
        }
    }

    private static final Map<Locale, List<PluralRule>> pluralRules =
        new HashMap<>();

    private static final Map<Locale, Path> dataFiles = new HashMap<>();

    private static final Path cldrDir;

    static {
        String dir = System.getProperty("cldr");
        if (dir == null) {
            throw new RuntimeException(
                "\"cldr\" system property must be set to root directory"
                + " of CLDR data.  That data can be downloaded from"
                + "https://cldr.unicode.org/index/downloads or"
                + "https://github.com/unicode-org/cldr .");
        }
        cldrDir = Paths.get(dir);
    }

    private final XPath xpath;

    public PeriodFormatter() {
        this.xpath = XPathFactory.newInstance().newXPath();
    }

    private static InputSource createSource(Path path) {
        return new InputSource(path.toUri().toASCIIString());
    }

    private Count countFor(BigDecimal amount,
                           Locale locale) {

        synchronized (pluralRules) {
            if (pluralRules.isEmpty()) {
                Path pluralsFile = cldrDir.resolve(
                    Paths.get("common", "supplemental", "plurals.xml"));

                NodeList rulesElements;
                try {
                    rulesElements = (NodeList) xpath.evaluate(
                        "//plurals[@type='cardinal']/pluralRules",
                        createSource(pluralsFile),
                        XPathConstants.NODESET);
                } catch (XPathException e) {
                    throw new RuntimeException(e);
                }

                int count = rulesElements.getLength();
                for (int i = 0; i < count; i++) {
                    Element rulesElement = (Element) rulesElements.item(i);
                    String[] localeNames =
                        rulesElement.getAttribute("locales").split("\\s+");

                    NodeList ruleElements =
                        rulesElement.getElementsByTagName("pluralRule");
                    int ruleCount = ruleElements.getLength();
                    List<PluralRule> ruleList = new ArrayList<>(ruleCount);
                    for (int j = 0; j < ruleCount; j++) {
                        Element ruleElement = (Element) ruleElements.item(j);
                        ruleList.add(new PluralRule(
                            ruleElement.getAttribute("count"),
                            ruleElement.getTextContent()));
                    }

                    for (String localeName : localeNames) {
                        localeName = localeName.replace('_', '-');
                        pluralRules.put(
                            Locale.forLanguageTag(localeName),
                            ruleList);
                    }
                }
            }
        }

        Locale availableLocale = Locale.lookup(
            Locale.LanguageRange.parse(locale.toLanguageTag()),
            pluralRules.keySet());

        if (availableLocale == null) {
            availableLocale = Locale.ROOT;
        }

        List<PluralRule> rulesOfLocale = pluralRules.get(availableLocale);
        for (PluralRule rule : rulesOfLocale) {
            if (rule.matches(amount)) {
                return rule.count;
            }
        }
        throw new IllegalArgumentException("No plural rule matches " + amount);
    }

    private static List<Locale> listWithFallbacks(Locale locale) {
        Collection<Locale> locales = new LinkedHashSet<>();
        locales.add(locale);

        Locale.Builder builder = new Locale.Builder();
        builder.setLanguageTag(locale.toLanguageTag());

        locales.add(builder.setVariant(null).build());
        locales.add(builder.setRegion(null).build());
        locales.add(builder.setScript(null).build());
        locales.add(Locale.ROOT);

        return new ArrayList<>(locales);
    }

    private Iterable<Path> dataFilesFor(Locale locale) {
        synchronized (dataFiles) {
            if (dataFiles.isEmpty()) {
                Path dataFileDir = cldrDir.resolve(Paths.get("common", "main"));
                try (DirectoryStream<Path> dir =
                    Files.newDirectoryStream(dataFileDir, "*.xml")) {

                    for (Path dataFile : dir) {
                        InputSource source = createSource(dataFile);

                        NodeList identityElements = (NodeList)
                            xpath.evaluate("/ldml/identity", source,
                                XPathConstants.NODESET);
                        Element identity = (Element) identityElements.item(0);

                        String lang =
                            xpath.evaluate("language/@type", identity);
                        String script =
                            xpath.evaluate("script/@type", identity);
                        String region =
                            xpath.evaluate("territory/@type", identity);
                        String variant =
                            xpath.evaluate("variant/@type", identity);

                        Locale dataFileLocale;
                        if (lang.equals("root")) {
                            dataFileLocale = Locale.ROOT;
                        } else {
                            Locale.Builder builder = new Locale.Builder();
                            builder.setLanguage(lang);
                            builder.setScript(script);
                            builder.setRegion(region);
                            builder.setVariant(variant);
                            dataFileLocale = builder.build();
                        }

                        dataFiles.put(dataFileLocale, dataFile);
                    }
                } catch (IOException | XPathException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        Collection<Locale> locales = listWithFallbacks(locale);

        Collection<Path> dataFilesForLocale = new ArrayList<>();
        for (Locale localeToCheck : locales) {
            Path dataFile = dataFiles.get(localeToCheck);
            if (dataFile != null) {
                dataFilesForLocale.add(dataFile);
            }
        }
        return dataFilesForLocale;
    }

    private Optional<Element> locateElement(Object source,
                                            String path) {

        try {
            Element element = null;
            while (true) {
                NodeList elements;
                if (source instanceof InputSource) {
                    elements = (NodeList) xpath.evaluate(path,
                        (InputSource) source, XPathConstants.NODESET);
                } else {
                    elements = (NodeList) xpath.evaluate(path,
                        source, XPathConstants.NODESET);
                }
                if (elements.getLength() < 1) {
                    break;
                }

                element = (Element) elements.item(0);

                NodeList list = (NodeList) xpath.evaluate("alias", element,
                    XPathConstants.NODESET);
                if (list.getLength() == 0) {
                    // No more aliases to follow, so we've found our target.
                    break;
                }

                Element alias = (Element) list.item(0);
                path = alias.getAttribute("path");
                source = element;
            }

            return Optional.ofNullable(element);
        } catch (XPathException e) {
            throw new RuntimeException(e);
        }
    }

    private Optional<String> readElement(Iterable<? extends Path> dataFiles,
                                         String... paths)
    throws XPathException {

        Optional<Element> element = Optional.empty();
        for (Path dataFile : dataFiles) {
            Object source = createSource(dataFile);

            element = Optional.empty();
            for (String path : paths) {
                element = locateElement(source, path);
                if (!element.isPresent()) {
                    break;
                }

                source = element.get();
            }

            if (element.isPresent()) {
                break;
            }
        }

        return element.map(Element::getTextContent);
    }

    private String format(ChronoUnit units,
                          BigDecimal amount,
                          Locale locale) {

        String type = null;
        switch (units) {
            case YEARS:
                type = "duration-year";
                break;
            case MONTHS:
                type = "duration-month";
                break;
            case WEEKS:
                type = "duration-week";
                break;
            case DAYS:
                type = "duration-day";
                break;
            default:
                throw new IllegalArgumentException(
                    "Valid units are YEARS, MONTHS, WEEKS, and DAYS.");
        }

        Count count = countFor(amount, locale);

        try {
            Optional<String> formatPattern = readElement(dataFilesFor(locale),
                "/ldml/units" +
                    "/unitLength[@type='long']" +
                    "/unit[@type='" + type + "']",
                "unitPattern[@count='" + count.attributeValue() + "']");

            if (formatPattern.isPresent()) {
                String patternStr = formatPattern.get();
                if (!patternStr.isEmpty()) {
                    MessageFormat format =
                        new MessageFormat(patternStr, locale);
                    return format.format(new Object[] { amount });
                }
            }
        } catch (XPathException e) {
            throw new RuntimeException(e);
        }

        throw new IllegalArgumentException(
                "Could not find pattern for units " + units +
                ", amount " + amount + ", locale " + locale.toLanguageTag());
    }

    public String format(Period period,
                         Locale locale) {
        return format(period, false, locale);
    }

    public String format(Period period,
                         boolean includeWeeks,
                         Locale locale) {

        int years = period.getYears();
        int months = period.getMonths();
        int days = period.getDays();

        List<String> parts = new ArrayList<>(4);

        if (years != 0) {
            BigDecimal value = BigDecimal.valueOf(years);
            String yearStr = format(ChronoUnit.YEARS, value, locale);
            parts.add(yearStr);
        }
        if (months != 0) {
            BigDecimal value = BigDecimal.valueOf(months);
            String monthStr = format(ChronoUnit.MONTHS, value, locale);
            parts.add(monthStr);
        }
        if (includeWeeks) {
            int weeks = days / 7;
            if (weeks != 0) {
                days %= 7;
                BigDecimal value = BigDecimal.valueOf(weeks);
                String weekStr = format(ChronoUnit.WEEKS, value, locale);
                parts.add(weekStr);
            }
        }
        if (days != 0) {
            BigDecimal value = BigDecimal.valueOf(days);
            String dayStr = format(ChronoUnit.DAYS, value, locale);
            parts.add(dayStr);
        }

        return formatList(parts, locale);
    }

    private String formatList(List<?> parts,
                              Locale locale) {

        if (parts.isEmpty()) {
            return "";
        }
        int size = parts.size();
        if (size == 1) {
            return String.valueOf(parts.get(0));
        }

        Map<String, String> patternsByType = new HashMap<>();
        for (Path dataFile : dataFilesFor(locale)) {
            Object source = createSource(dataFile);

            Optional<Element> listPatterns =
                locateElement(source, "/ldml/listPatterns");

            if (!listPatterns.isPresent()) {
                continue;
            }

            Optional<Element> listPattern =
                locateElement(listPatterns.get(), "listPattern[@type='unit']");

            if (!listPattern.isPresent()) {
                continue;
            }

            NodeList partList =
                listPattern.get().getElementsByTagName("listPatternPart");

            int count = partList.getLength();
            for (int i = 0; i < count; i++) {
                Element part = (Element) partList.item(i);
                String type = part.getAttribute("type");
                String pattern = part.getTextContent();
                patternsByType.putIfAbsent(type, pattern);
            }
        }

        if (size == 2 || size == 3) {
            String pattern = patternsByType.get(String.valueOf(size));
            if (pattern != null) {
                MessageFormat format =new MessageFormat(pattern, locale);
                return format.format(parts.toArray());
            }
        }

        MessageFormat startFormat = new MessageFormat(
            patternsByType.get("start"), locale);
        MessageFormat middleFormat = new MessageFormat(
            patternsByType.get("middle"), locale);
        MessageFormat endFormat = new MessageFormat(
            patternsByType.get("end"), locale);

        String text = endFormat.format(
            new Object[] { parts.get(size - 2), parts.get(size - 1) });

        int index = size - 2;
        while (--index > 0) {
            text = middleFormat.format(new Object[] { parts.get(index), text });
        }
        text = startFormat.format(new Object[] { parts.get(index), text });

        return text;
    }

    public static void main(String[] args) {
        String localeStr = System.getProperty("locale");
        Locale locale = localeStr != null ?
            Locale.forLanguageTag(localeStr) : Locale.getDefault();

        boolean showWeeks = Boolean.getBoolean("weeks");

        PeriodFormatter formatter = new PeriodFormatter();

        for (String arg : args) {
            Period period = Period.parse(arg);
            String s = formatter.format(period, showWeeks, locale);
            System.out.println(arg + " => " + s);
        }
    }
}
Retroact answered 7/10, 2022 at 15:4 Comment(0)
A
-2

Sorry twice it seems, but i'd leave this post here, that problem is a terrible fright about calender display name fields, back in 2010 i spent a couple of days looking for the hr,min,sec displayneames until i got info from Sun Microsystems forum there were no names for some. The API doc info almost says that, just not clearly. Niether does it say anything clearly for java.time.temporal.TemporalField and java.time.temporal.ChronoField and its method getDisplayName(Locale locale) Sorry(deleted other answer), I thought you meant "time" itself, you meant "time UNITS" that are part of java TemporalUnit of the Duration is a Temporal with TemporalUnit's AND do have "name retrieval methods" but translated, no. However, java.util.Calendar also has "time units" AND can be instantiated with Locale and should have class fields and methods that will return "time unit" foreign language names

This is a test printer class for a field that does have a display name. The commented out code prints "null" returned by getDisplayName()

// CalendarTest.java
public class CalendarTest{

int[] ipt = {java.util.Calendar.DAY_OF_WEEK,java.util.Calendar.MONTH,java.util.Calendar.ERA,java.util.Calendar.AM_PM};

public CalendarTest(){

//java.time.LocalDateTime tm = java.time.LocalDateTime.now();   +java.util.Calendar.HOUR   DAY_OF_WEEK

for(int sd=0;sd<ipt.length;sd++){
pr(ipt[sd],"fr");
}

for(int sd1=0;sd1<ipt.length;sd1++){
pr(ipt[sd1],"zh");
}

for(int sd2=0;sd2<ipt.length;sd2++){
pr(ipt[sd2],"ar");
}

/*
//java.util.Date dt = cal.getTime();
System.out.println(""+cal.get(java.util.Calendar.HOUR));
String fieldname = cal.getDisplayName(java.util.Calendar.HOUR_OF_DAY, java.util.Calendar.SHORT, new java.util.Locale("en"));
System.out.println(fieldname);
fieldname = cal.getDisplayName(java.util.Calendar.HOUR_OF_DAY, java.util.Calendar.LONG, new java.util.Locale("en"));
System.out.println(fieldname);
*/
}//enconstr

public void pr(int fieldint,String loc){
java.util.Calendar cal = java.util.Calendar.getInstance(new java.util.Locale(loc));
java.util.Map<String,Integer> mp = cal.getDisplayNames(fieldint , java.util.Calendar.ALL_STYLES, new java.util.Locale(loc));
if(mp.isEmpty()==true){
System.out.println("Is Empty");
}else{
System.out.println("Contains");
}
java.util.Set<String> st = mp.keySet();
Object[] sta = st.toArray();
for(int fs=0;fs<sta.length;fs++){
System.out.println("A VALUE : "+sta[fs]);
}

}//enmeth

public static void main(String[] args){
new CalendarTest();
}//enmain

}//enclss


/* prints Special field values and in Locale but not field names

bash-5.1$ java CalendarTest
Contains
A VALUE : dimanche
A VALUE : mercredi
A VALUE : vendredi
A VALUE : samedi
A VALUE : jeudi
A VALUE : lundi
A VALUE : mardi
A VALUE : dim.
A VALUE : jeu.
A VALUE : lun.
A VALUE : mar.
A VALUE : mer.
A VALUE : sam.
A VALUE : ven.
Contains
A VALUE : septembre
A VALUE : décembre
A VALUE : novembre
A VALUE : février
A VALUE : janvier
A VALUE : juillet
A VALUE : octobre
A VALUE : avril
A VALUE : févr.
A VALUE : janv.
A VALUE : juil.
A VALUE : sept.
A VALUE : août
A VALUE : avr.
A VALUE : déc.
A VALUE : juin
A VALUE : mars
A VALUE : nov.
A VALUE : oct.
A VALUE : mai
Contains
A VALUE : ap. J.-C.
A VALUE : av. J.-C.
A VALUE : BC
A VALUE : A
A VALUE : B
Contains
A VALUE : AM
A VALUE : PM
A VALUE : a
A VALUE : p
Contains
A VALUE : 星期一
A VALUE : 星期三
A VALUE : 星期二
A VALUE : 星期五
A VALUE : 星期六
A VALUE : 星期四
A VALUE : 星期日
A VALUE : 一
A VALUE : 三
A VALUE : 二
A VALUE : 五
A VALUE : 六
A VALUE : 四
A VALUE : 日
Contains
A VALUE : 10月
A VALUE : 11月
A VALUE : 12月
A VALUE : 十一月
A VALUE : 十二月
A VALUE : 10
A VALUE : 11
A VALUE : 12
A VALUE : 1月
A VALUE : 2月
A VALUE : 3月
A VALUE : 4月
A VALUE : 5月
A VALUE : 6月
A VALUE : 7月
A VALUE : 8月
A VALUE : 9月
A VALUE : 一月
A VALUE : 七月
A VALUE : 三月
A VALUE : 九月
A VALUE : 二月
A VALUE : 五月
A VALUE : 八月
A VALUE : 六月
A VALUE : 十月
A VALUE : 四月
A VALUE : 1
A VALUE : 2
A VALUE : 3
A VALUE : 4
A VALUE : 5
A VALUE : 6
A VALUE : 7
A VALUE : 8
A VALUE : 9
Contains
A VALUE : 公元前
A VALUE : AD
A VALUE : BC
A VALUE : 公元
A VALUE : A
A VALUE : B
Contains
A VALUE : 上午
A VALUE : 下午
A VALUE : a
A VALUE : p
Contains
A VALUE : الأربعاء
A VALUE : الثلاثاء
A VALUE : الاثنين
A VALUE : الجمعة
A VALUE : الخميس
A VALUE : الأحد
A VALUE : السبت
A VALUE : ث
A VALUE : ج
A VALUE : ح
A VALUE : خ
A VALUE : ر
A VALUE : س
A VALUE : ن
Contains
A VALUE : أكتوبر
A VALUE : ديسمبر
A VALUE : سبتمبر
A VALUE : فبراير
A VALUE : نوفمبر
A VALUE : أبريل
A VALUE : أغسطس
A VALUE : يناير
A VALUE : يوليو
A VALUE : يونيو
A VALUE : مارس
A VALUE : مايو
A VALUE : أبر
A VALUE : أغس
A VALUE : أكت
A VALUE : ديس
A VALUE : سبت
A VALUE : فبر
A VALUE : مار
A VALUE : ماي
A VALUE : نوف
A VALUE : ينا
A VALUE : يول
A VALUE : يون
A VALUE : أ
A VALUE : ب
A VALUE : د
A VALUE : س
A VALUE : غ
A VALUE : ف
A VALUE : ك
A VALUE : ل
A VALUE : م
A VALUE : ن
A VALUE : و
A VALUE : ي
Contains
A VALUE : ق.م
A VALUE : A
A VALUE : B
A VALUE : م
Contains
A VALUE : a
A VALUE : p
A VALUE : ص
A VALUE : م
bash-5.1$ 


*/

The below code does not operate (as OleV.V. said) because some fields do not get a SHORT or LONG display name.

java.util.Calendar cal =  java.util.Calendar.getInstance( java.util.TimeZone.getTimeZone("Asia/Shanghai") , java.util.Locale.CHINA);
String hourof = cal.getDisplayName(java.util.Calendar.HOUR , java.util.Calendar.LONG , java.util.Locale.CHINA);

Displaying it in html use attribute lang="zh" in the text rendering element tag e.g. div , input , td UTF-8 is the normal charset to use bound to a locale although for Chinese it may be UTF-16

Take a look at the "field summary" in the API docs for java.util.Calendar.

Week, appears to be a problem but int SHORT could display simply "week" perhaps.

Altocumulus answered 5/10, 2022 at 15:49 Comment(3)
Again, adding a code example to your answer would help. I am not aware of any way to use Calendar to create a localized human-readable form of a time period like the examples in the question.Retroact
Your getDisplayName() call returned null on my Java 11.Foretell
Have'nt / cannot check in for some time...back soon I hopeAltocumulus

© 2022 - 2024 — McMap. All rights reserved.