Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public class CLDRConverter {
static final String EXEMPLAR_CITY_PREFIX = "timezone.excity.";
static final String ZONE_NAME_PREFIX = "timezone.displayname.";
static final String METAZONE_ID_PREFIX = "metazone.id.";
static final String METAZONE_DSTOFFSET_PREFIX = "metazone.dstoffset.";
static final String PARENT_LOCALE_PREFIX = "parentLocale.";
static final String LIKELY_SCRIPT_PREFIX = "likelyScript.";
static final String META_EMPTY_ZONE_NAME = "EMPTY_ZONE";
Expand Down Expand Up @@ -139,6 +140,11 @@ public class CLDRConverter {
private static final Map<String, String> tzdbSubstLetters = HashMap.newHashMap(512);
private static final Map<String, String> tzdbLinks = HashMap.newHashMap(512);

// Map of explicit dst offsets for metazones
// key: time zone ID
// value: explicit dstOffset for the corresponding metazone name
static final Map<String, String> explicitDstOffsets = HashMap.newHashMap(32);

static enum DraftType {
UNCONFIRMED,
PROVISIONAL,
Expand Down Expand Up @@ -855,6 +861,12 @@ private static Map<String, Object> extractZoneNames(Map<String, Object> map, Str
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
names.putAll(exCities);

// Explicit metazone offsets
if (id.equals("root")) {
explicitDstOffsets.forEach((k, v) ->
names.put(METAZONE_DSTOFFSET_PREFIX + k, v));
}

// If there's no UTC entry at this point, add an empty one
if (!names.isEmpty() && !names.containsKey("UTC")) {
names.putIfAbsent(METAZONE_ID_PREFIX + META_EMPTY_ZONE_NAME, EMPTY_ZONE);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, 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
Expand Down Expand Up @@ -84,7 +84,15 @@ public void startElement(String uri, String localName, String qName, Attributes

if (fromLDT.isBefore(now) && toLDT.isAfter(now)) {
metazone = attributes.getValue("mzone");

// Explicit metazone DST offsets. Only the "dst" offset is needed,
// as "std" is used by default when it doesn't match.
String dstOffset = attributes.getValue("dstOffset");
if (dstOffset != null) {
CLDRConverter.explicitDstOffsets.put(tzid, dstOffset);
}
}

pushIgnoredContainer(qName);
break;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, 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
Expand Down Expand Up @@ -198,7 +198,8 @@ public void generateBundle(String packageName, String baseName, String localeID,
} else if (value instanceof String) {
String valStr = (String)value;
if (type == BundleType.TIMEZONE &&
!key.startsWith(CLDRConverter.EXEMPLAR_CITY_PREFIX) ||
!(key.startsWith(CLDRConverter.EXEMPLAR_CITY_PREFIX) ||
key.startsWith(CLDRConverter.METAZONE_DSTOFFSET_PREFIX)) ||
valStr.startsWith(META_VALUE_PREFIX)) {
out.printf(" { \"%s\", %s },\n", key, CLDRConverter.saveConvert(valStr, useJava));
} else {
Expand Down
27 changes: 17 additions & 10 deletions src/java.base/share/classes/java/text/SimpleDateFormat.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2026, 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
Expand Down Expand Up @@ -41,7 +41,7 @@
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import static java.text.DateFormatSymbols.*;
import java.time.ZoneOffset;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
Expand All @@ -57,6 +57,8 @@
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.TimeZoneNameUtility;

import static java.text.DateFormatSymbols.*;

/**
* {@code SimpleDateFormat} is a concrete class for formatting and
* parsing dates in a locale-sensitive manner. It allows for formatting
Expand Down Expand Up @@ -1293,15 +1295,22 @@ private void subFormat(int patternCharIndex, int count,

case PATTERN_ZONE_NAME: // 'z'
if (current == null) {
TimeZone tz = calendar.getTimeZone();
String tzid = tz.getID();
int zoneOffset = calendar.get(Calendar.ZONE_OFFSET);
int dstOffset = calendar.get(Calendar.DST_OFFSET) + zoneOffset;

// Check if an explicit metazone DST offset exists
String explicitDstOffset = TimeZoneNameUtility.explicitDstOffset(tzid);
boolean daylight = explicitDstOffset != null ?
dstOffset == ZoneOffset.of(explicitDstOffset).getTotalSeconds() * 1_000 :
dstOffset != zoneOffset;
if (formatData.locale == null || formatData.isZoneStringsSet) {
int zoneIndex =
formatData.getZoneIndex(calendar.getTimeZone().getID());
int zoneIndex = formatData.getZoneIndex(tzid);
if (zoneIndex == -1) {
value = calendar.get(Calendar.ZONE_OFFSET) +
calendar.get(Calendar.DST_OFFSET);
buffer.append(ZoneInfoFile.toCustomID(value));
buffer.append(ZoneInfoFile.toCustomID(dstOffset));
} else {
int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;
int index = daylight ? 3 : 1;
if (count < 4) {
// Use the short name
index++;
Expand All @@ -1310,8 +1319,6 @@ private void subFormat(int patternCharIndex, int count,
buffer.append(zoneStrings[zoneIndex][index]);
}
} else {
TimeZone tz = calendar.getTimeZone();
boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG);
buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, 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
Expand Down Expand Up @@ -4505,7 +4505,11 @@ public boolean format(DateTimePrintContext context, StringBuilder buf) {
TemporalAccessor dt = context.getTemporal();
int type = GENERIC;
if (!isGeneric) {
if (dt.isSupported(ChronoField.INSTANT_SECONDS)) {
// Check if an explicit metazone DST offset exists
String dstOffset = TimeZoneNameUtility.explicitDstOffset(zname);
if (dt.isSupported(OFFSET_SECONDS) && dstOffset != null) {
type = ZoneOffset.from(dt).equals(ZoneOffset.of(dstOffset)) ? DST : STD;
} else if (dt.isSupported(ChronoField.INSTANT_SECONDS)) {
type = zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD;
} else if (dt.isSupported(ChronoField.EPOCH_DAY) &&
dt.isSupported(ChronoField.NANO_OF_DAY)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public class LocaleResources {
// TimeZoneNamesBundle exemplar city prefix
private static final String TZNB_EXCITY_PREFIX = "timezone.excity.";

// TimeZoneNamesBundle explicit metazone dst offset prefix
private static final String TZNB_METAZONE_DSTOFFSET_PREFIX = "metazone.dstoffset.";

// null singleton cache value
private static final Object NULLOBJECT = new Object();

Expand Down Expand Up @@ -333,7 +336,8 @@ public Object getTimeZoneNames(String key) {

if (Objects.isNull(data) || Objects.isNull(val = data.get())) {
TimeZoneNamesBundle tznb = localeData.getTimeZoneNames(locale);
if (key.startsWith(TZNB_EXCITY_PREFIX)) {
if (key.startsWith(TZNB_EXCITY_PREFIX) ||
key.startsWith(TZNB_METAZONE_DSTOFFSET_PREFIX)) {
if (tznb.containsKey(key)) {
val = tznb.getString(key);
assert val instanceof String;
Expand Down Expand Up @@ -390,7 +394,8 @@ String[][] getZoneStrings() {
Set<String[]> value = new LinkedHashSet<>();
Set<String> tzIds = new HashSet<>(Arrays.asList(TimeZone.getAvailableIDs()));
for (String key : keyset) {
if (!key.startsWith(TZNB_EXCITY_PREFIX)) {
if (!key.startsWith(TZNB_EXCITY_PREFIX) &&
!key.startsWith(TZNB_METAZONE_DSTOFFSET_PREFIX)) {
value.add(rb.getStringArray(key));
tzIds.remove(key);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import java.util.spi.TimeZoneNameProvider;
import sun.util.calendar.ZoneInfo;
import sun.util.cldr.CLDRLocaleProviderAdapter;
import static sun.util.locale.provider.LocaleProviderAdapter.Type;
import static sun.util.locale.provider.LocaleProviderAdapter.Type.CLDR;

/**
* Utility class that deals with the localized time zone names
Expand Down Expand Up @@ -169,10 +169,22 @@ public static Optional<String> convertLDMLShortID(String shortID) {
* Returns the canonical ID for the given ID
*/
public static Optional<String> canonicalTZID(String id) {
return ((CLDRLocaleProviderAdapter)LocaleProviderAdapter.forType(Type.CLDR))
return ((CLDRLocaleProviderAdapter)LocaleProviderAdapter.forType(CLDR))
.canonicalTZID(id);
}

/**
* {@return the explicit metazone DST offset for the specified time zone ID, if exists}
* @param tzid the time zone ID
*/
public static String explicitDstOffset(String tzid) {
return (String) (LocaleProviderAdapter.forType(CLDR) instanceof CLDRLocaleProviderAdapter ca ?
ca.getLocaleResources(Locale.ROOT)
.getTimeZoneNames("metazone.dstoffset." +
ca.canonicalTZID(tzid).orElse(tzid)) :
null);
}

private static String[] retrieveDisplayNamesImpl(String id, Locale locale) {
LocaleServiceProviderPool pool =
LocaleServiceProviderPool.getPool(TimeZoneNameProvider.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2026, 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
Expand Down Expand Up @@ -43,8 +43,6 @@
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Set;

/**
Expand Down
35 changes: 34 additions & 1 deletion test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,29 @@
/*
* @test
* @bug 8181157 8202537 8234347 8236548 8261279 8322647 8174269 8346948
* 8382020 8384043
* 8382020 8384043 8381379
* @modules jdk.localedata
* @summary Checks CLDR time zone names are generated correctly at
* either build or runtime
* @run junit TimeZoneNamesTest
*/

import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.TimeZone;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
Expand Down Expand Up @@ -263,6 +269,18 @@ private static Object[][] sampleTZs() {
};
}

private static Stream<Arguments> explicitDstOffsets() {
return Stream.of(
Arguments.of(ZonedDateTime.of(2026, 4, 5, 0, 0, 0, 0, ZoneId.of("Europe/Dublin")), "Irish Standard Time"),
Arguments.of(ZonedDateTime.of(2026, 12, 5, 0, 0, 0, 0, ZoneId.of("Europe/Dublin")), "Greenwich Mean Time"),
Arguments.of(ZonedDateTime.of(2026, 4, 5, 0, 0, 0, 0, ZoneId.of("Eire")), "Irish Standard Time"),
Arguments.of(ZonedDateTime.of(2026, 12, 5, 0, 0, 0, 0, ZoneId.of("Eire")), "Greenwich Mean Time"),
Arguments.of(ZonedDateTime.of(2026, 4, 5, 0, 0, 0, 0, ZoneId.of("America/Vancouver")), "Pacific Daylight Time"),
// This needs to change once TZDB adopts -7 offset year round, and CLDR uses explicit dst offset
// namely, "Pacific Standard Time" -> "Pacific Daylight Time"
Arguments.of(ZonedDateTime.of(2026, 12, 5, 0, 0, 0, 0, ZoneId.of("America/Vancouver")), "Pacific Standard Time")
);
}

@ParameterizedTest
@MethodSource("sampleTZs")
Expand Down Expand Up @@ -294,4 +312,19 @@ public void test_getZoneStrings() {
.anyMatch(name -> Objects.isNull(name) || name.isEmpty()),
"getZoneStrings() returned array containing non-empty string element(s)");
}

// Explicit metazone dst offset test. As of CLDR v48, only Europe/Dublin utilizes
// this attribute, but will be used for America/Vancouver once CLDR adopts the
// explicit offset for that zone, which warrants the test data modification.
@ParameterizedTest
@MethodSource("explicitDstOffsets")
public void test_ExplicitMetazoneOffsets(ZonedDateTime zdt, String expected) {
// java.time
assertEquals(expected, DateTimeFormatter.ofPattern("zzzz").format(zdt));

// java.text/util
var sdf = new SimpleDateFormat("zzzz");
sdf.setTimeZone(TimeZone.getTimeZone(zdt.getZone()));
assertEquals(expected, sdf.format(Date.from(zdt.toInstant())));
}
}