Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a001c19
Add testLang1641()
garydgregory Jul 19, 2024
abb0ca4
Rename some test methods
garydgregory Jul 19, 2024
73e4c9d
Merge remote-tracking branch 'upstream/master'
garydgregory Jul 19, 2024
0bcc867
Merge remote-tracking branch 'upstream/master'
garydgregory Jul 20, 2024
3484d8a
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 3, 2025
dec6a36
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 5, 2025
d959b47
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 11, 2025
78cfdd3
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 17, 2025
dddab49
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 18, 2025
550d4b9
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 25, 2025
22e4e5e
Merge remote-tracking branch 'upstream/master'
garydgregory Nov 27, 2025
a5bcad2
Merge remote-tracking branch 'upstream/master'
garydgregory Jan 9, 2026
9c606b5
Merge remote-tracking branch 'upstream/master'
garydgregory Jan 9, 2026
17d776d
Merge remote-tracking branch 'upstream/master'
garydgregory Jan 9, 2026
cddcb72
Merge remote-tracking branch 'upstream/master'
garydgregory Jan 10, 2026
d7a972c
Merge remote-tracking branch 'upstream/master'
garydgregory Jan 25, 2026
1309d96
Merge remote-tracking branch 'upstream/master'
garydgregory Feb 21, 2026
102fcb0
Merge remote-tracking branch 'upstream/master'
garydgregory Apr 15, 2026
7522574
Merge remote-tracking branch 'upstream/master'
garydgregory Apr 15, 2026
eb393f1
Merge remote-tracking branch 'upstream/master'
garydgregory Apr 21, 2026
307d875
Merge remote-tracking branch 'upstream/master'
garydgregory Apr 25, 2026
528eb8b
Merge remote-tracking branch 'upstream/master'
garydgregory Apr 29, 2026
833a992
[LANG-1823] LocaleUtils.toLocale cannot parse valid JDK Locale string
garydgregory Apr 29, 2026
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
13 changes: 10 additions & 3 deletions src/main/java/org/apache/commons/lang3/LocaleUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ static Locale ofCountry(final String country) {
* @return a Locale parsed from the given String.
* @throws IllegalArgumentException if the given String cannot be parsed.
* @see Locale
* @see <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Locale.html#special_cases_constructor">Locale special cases</a>
*/
private static Locale parseLocale(final String str) {
if (isISO639LanguageCode(str)) {
Expand All @@ -343,6 +344,14 @@ private static Locale parseLocale(final String str) {
} else if (segments.length == limit) {
final String country = segments[1];
final String variant = segments[2];
// Special case 1: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Locale.html#special_cases_constructor
if (str.equals("th_TH_TH_#u-nu-thai")) {
return new Locale(language, country, "TH");
}
// Special case 2: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Locale.html#special_cases_constructor
if (str.equals("ja_JP_JP_#u-ca-japanese")) {
return new Locale(language, country, "JP");
}
if (isISO639LanguageCode(language) && (country.isEmpty() || isISO3166CountryCode(country) || isNumericAreaCode(country)) && !variant.isEmpty()) {
return new Locale(language, country, variant);
}
Expand Down Expand Up @@ -396,6 +405,7 @@ public static Locale toLocale(final Locale locale) {
* @throws IllegalArgumentException if the string is an invalid format.
* @see Locale#forLanguageTag(String)
* @see Locale#getISOCountries()
* @see <a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Locale.html#special_cases_constructor">Locale special cases</a>
*/
public static Locale toLocale(final String str) {
if (str == null) {
Expand All @@ -405,9 +415,6 @@ public static Locale toLocale(final String str) {
if (str.isEmpty()) { // LANG-941 - JDK 8 introduced an empty locale where all fields are blank
return new Locale(StringUtils.EMPTY, StringUtils.EMPTY);
}
if (str.contains("#")) { // LANG-879 - Cannot handle Java 7 script & extensions
throw new IllegalArgumentException("Invalid locale format: " + str);
}
final int len = str.length();
if (len < 2) {
throw new IllegalArgumentException("Invalid locale format: " + str);
Expand Down
29 changes: 24 additions & 5 deletions src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@ void testIsLanguageUndetermined() {
assertTrue(LocaleUtils.isLanguageUndetermined(null));
}

/**
* Tests #LANG-1823
*/
@Test
void testLang1823() {
assertValidToLocale("th_TH_#Thai", "th", "TH", "#Thai");
}

/**
* Tests #LANG-328 - only language+variant
*/
Expand Down Expand Up @@ -417,23 +425,34 @@ void testParseAllLocales(final Locale actualLocale) {
// Check if it's possible to recreate the Locale using just the standard constructor
final Locale locale = new Locale(actualLocale.getLanguage(), actualLocale.getCountry(), actualLocale.getVariant());
if (actualLocale.equals(locale)) { // it is possible for LocaleUtils.toLocale to handle these Locales
assertEquals(actualLocale, LocaleUtils.toLocale(actualLocale.toString()));
final String str = actualLocale.toString();
// Look for the script/extension suffix
int suff = str.indexOf("_#");
if (suff == - 1) {
if (suff == -1) {
suff = str.indexOf("#");
}
String localeStr = str;
if (suff >= 0) { // we have a suffix
assertIllegalArgumentException(() -> LocaleUtils.toLocale(str));
// try without suffix
localeStr = str.substring(0, suff);
}
final Locale loc = LocaleUtils.toLocale(localeStr);
assertEquals(actualLocale, loc);
assertEquals(actualLocale, LocaleUtils.toLocale(localeStr));
}
}

/**
* Special cases from https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Locale.html#special_cases_constructor
*/
@Test
void testSpecialCases() {
assertValidToLocale("th_TH_TH", "th", "TH", "TH");
assertValidToLocale("ja_JP_JP", "ja", "JP", "JP");
// "th_TH_TH_#u-nu-thai" and friends
LocaleUtils.localeLookupList(new Locale("th", "TH", "TH")).forEach(locale -> assertEquals(locale, LocaleUtils.toLocale(locale.toString())));
// "ja_JP_JP_#u-ca-japanese" and friends
LocaleUtils.localeLookupList(new Locale("ja", "JP", "JP")).forEach(locale -> assertEquals(locale, LocaleUtils.toLocale(locale.toString())));
}

/**
* Test for 3-chars locale, further details at LANG-915
*/
Expand Down
Loading