tl;dr
LocalDate
.now()
.format(
DateTimeFormatter
.ofLocalizedDate( FormatStyle.MEDIUM ) )
.localizedBy( Locale.of( "no" , "NO" ) );
);
The troublesome classes of java.util.Date and SimpleDateFormat are now legacy, supplanted by the java.time classes.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( z );
DateTimeFormatter
Use DateTimeFormatter to generate strings representing only the date-portion or the time-portion.
The DateTimeFormatter class can automatically localize.
To localize, specify:
FormatStyle to determine how long or abbreviated should the string be.
Locale to determine (a) the human language for translation of name of day, name of month, and such, and (b) the cultural norms deciding issues of abbreviation, capitalization, punctuation, and such.
Example:
Locale l = Locale.of( "fr" , "CA" ) ; // French language, Canada culture.
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL ).localizedBy( l );
String output = ld.format( f );
Going the other direction, you can parse a localized string.
LocalDate ld = LocalDate.parse( input , f );
Note that the locale and time zone are completely orthogonal issues. You can have a Montréal moment presented in Japanese language or an Auckland New Zealand moment presented in Hindi language.
Another example: Change 6 junio 2012 (Spanish) to 2012-06-06 (standard ISO 8601 format). The java.time classes use ISO 8601 formats by default for parsing/generating strings.
String input = "6 junio 2012";
Locale l = Locale.of( "es" , "ES" ); // In earlier Java: Locale.forLanguageTag( "es-ES" )
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "d MMMM uuuu" , l );
LocalDate ld = LocalDate.parse ( input , f );
String output = ld.toString(); // 2012-06-06.
Peruse formats
Here is some example code for perusing the results of multiple formats in multiple locales, automatically localized.
An EnumSet is an implementation of Set, highly optimized for both low memory usage and fast execution speed when collecting Enum objects. So, EnumSet.allOf( FormatStyle.class ) gives us a collection of all four of the FormatStyle enum objects to loop. For more info, see Oracle Tutorial on enum types.
LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 23 );
List < Locale > locales = new ArrayList <>( 3 );
locales.add( Locale.of( "fr" , "CA" ) ); // French language, Canada culture.
locales.add( Locale.of( "no" , "NO" ) ); // Norwegian language, Norway culture.
locales.add( Locale.of( "en" , "US" ) ); // English language, United States culture.
// Or use all locales (almost 800 of them, for about 120K text results).
// Locale[] locales = Locale.getAvailableLocales(); // All known locales. Almost 800 of them.
for ( Locale locale : locales )
{
System.out.println( "------| LOCALE: " + locale + " — " + locale.getDisplayName() + " |----------------------------------" + System.lineSeparator() );
for ( FormatStyle style : EnumSet.allOf( FormatStyle.class ) )
{
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( style ).localizedBy( locale );
String output = ld.format( f );
System.out.println( output );
}
System.out.println( "" );
}
System.out.println( "« fin »" + System.lineSeparator() );
Output.
------| LOCALE: fr_CA — French (Canada) |----------------------------------
mardi 23 janvier 2018
23 janvier 2018
23 janv. 2018
18-01-23
------| LOCALE: no_NO — Norwegian (Norway) |----------------------------------
tirsdag 23. januar 2018
23. januar 2018
23. jan. 2018
23.01.2018
------| LOCALE: en_US — English (United States) |----------------------------------
Tuesday, January 23, 2018
January 23, 2018
Jan 23, 2018
1/23/18
« fin »
withLocale versus localizedBy
By the way, let's talk about localizing DateTimeFormatter.
The original java.time classes in Java 8 came with the method DateTimeFormatter#withLocale. This lets you specify a a Locale to use during localization instead of implicitly using the JVM’s current default Locale.
Later, in Java 10, the DateTimeFormatter class gained the localizedBy method. This method may override some aspects of your specified formatting pattern, for deeper effects of localization. For example, rather than use Western Arabic numerals (0-9), the formatter employs the digits of that locale’s script. You may or may not want these effects.
While I have not yet given this much consideration, and I am no expert on localization, my initial thoughts are:
- If you are specifying a formatting pattern, you probably want this exact pattern. If so, call
withLocale rather than localizedBy.
- If you want deeper localization, do not specify a formatting pattern. Instead of an explicit pattern, let
DateTimeFormatter determine the appropriate format by calling one of the ofLocalized… methods. Then call localizedBy to get the deeper localization effects. With this approach in Java 25 we see in code below that of the 1,158 locales available, exactly 100 locales show a difference when using localizedBy versus withLocale for ZonedDateTime with a soft-coded formatter.
That would be my general suggestion. Of course, I am sure that in particular circumstances, depending on the needs and expectations of your users and stakeholders, you may want to otherwise combine soft-coding/hard-coding formats with localizedBy/withLocale.
Here is some example code showing these differences of localizedBy versus withLocale across all locales. Have fun playing around with this code.
package work.basil.example.time;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;
public class ExLocalizedBy
{
static void main ( )
{
IO.println( "Runtime.version: " + Runtime.version() );
ZoneId z = ZoneId.systemDefault();
ZonedDateTime zdt = ZonedDateTime.now( z );
//List < Locale > locales =
// List.of(
// Locale.of( "fr" , "CA" ) , // French language, Canada culture.
// Locale.of( "no" , "NO" ) , // Norwegian language, Norway culture.
// Locale.of( "en" , "US" ) // English language, United States culture.
// );
List < Locale > locales = List.of( Locale.getAvailableLocales() ); // All known locales. Almost 800 of them.
IO.println( "locales.length = " + locales.size() );
int countDifferences = 0;
Set < Locale > localesWithAnyDifferences = new HashSet <>( locales.size() );
for ( Locale locale : locales )
{
//IO.println( "------| LOCALE: " + locale + " — " + locale.getDisplayName() + " |----------------------------------" + System.lineSeparator() );
for ( FormatStyle style : EnumSet.allOf( FormatStyle.class ) )
{
DateTimeFormatter f = DateTimeFormatter.ofPattern( "…" );
// Old-way: DateTimeFormatter#withLocale
DateTimeFormatter fWithLocale =
DateTimeFormatter
.ofLocalizedDate( style )
.withLocale( locale ); // ⬅️ Critical line.
String outputWithLocale = zdt.format( fWithLocale );
// New-way: DateTimeFormatter#localizedBy
DateTimeFormatter fLocalizedBy =
DateTimeFormatter
.ofLocalizedDate( style )
.localizedBy( locale ); // ⬅️ Critical line.
String outputLocalizedBy = zdt.format( fLocalizedBy );
if ( ! outputWithLocale.contentEquals( outputLocalizedBy ) )
{
countDifferences++;
localesWithAnyDifferences.add( locale ); // `Set` eliminates duplicates.
IO.println( "DIFFERENCE DETECTED for style " + style + ": locale = " + locale + " `withLocale`: " + outputWithLocale + " versus `localizedBy`: " + outputLocalizedBy );
}
}
}
IO.println( "countDifferences = " + countDifferences );
IO.println( "localesWithAnyDifferences.size() = " + localesWithAnyDifferences.size() );
localesWithAnyDifferences.forEach( locale -> IO.println( locale.toString() + " | " + locale.getDisplayName() ) );
IO.println( "« fin »" + System.lineSeparator() );
}
}
Here is an excerpt of output.
Runtime.version: 25+35-3488
locales.length = 1158
DIFFERENCE DETECTED for style FULL: locale = ar_BH `withLocale`: السبت، 23 أغسطس 2025 versus `localizedBy`: السبت، ٢٣ أغسطس ٢٠٢٥
DIFFERENCE DETECTED for style LONG: locale = ar_BH `withLocale`: 23 أغسطس 2025 versus `localizedBy`: ٢٣ أغسطس ٢٠٢٥
DIFFERENCE DETECTED for style MEDIUM: locale = ar_BH `withLocale`: 23/08/2025 versus `localizedBy`: ٢٣/٠٨/٢٠٢٥
DIFFERENCE DETECTED for style SHORT: locale = ar_BH `withLocale`: 23/8/2025 versus `localizedBy`: ٢٣/٨/٢٠٢٥
DIFFERENCE DETECTED for style FULL: locale = raj `withLocale`: 2025 अगस्त 23, शनिवार versus `localizedBy`: २०२५ अगस्त २३, शनिवार
DIFFERENCE DETECTED for style LONG: locale = raj `withLocale`: 2025 अगस्त 23 versus `localizedBy`: २०२५ अगस्त २३
DIFFERENCE DETECTED for style MEDIUM: locale = raj `withLocale`: 2025 अगस्त 23 versus `localizedBy`: २०२५ अगस्त २३
DIFFERENCE DETECTED for style SHORT: locale = raj `withLocale`: 2025-08-23 versus `localizedBy`: २०२५-०८-२३
DIFFERENCE DETECTED for style FULL: locale = ne_NP_#Deva `withLocale`: 2025 अगस्ट 23, शनिबार versus `localizedBy`: २०२५ अगस्ट २३, शनिबार
DIFFERENCE DETECTED for style LONG: locale = ne_NP_#Deva `withLocale`: 2025 अगस्ट 23 versus `localizedBy`: २०२५ अगस्ट २३
…
DIFFERENCE DETECTED for style LONG: locale = bgc_IN `withLocale`: 2025 अगस्त 23 versus `localizedBy`: २०२५ अगस्त २३
DIFFERENCE DETECTED for style MEDIUM: locale = bgc_IN `withLocale`: 2025 अगस्त 23 versus `localizedBy`: २०२५ अगस्त २३
DIFFERENCE DETECTED for style SHORT: locale = bgc_IN `withLocale`: 2025-08-23 versus `localizedBy`: २०२५-०८-२३
countDifferences = 400
localesWithAnyDifferences.size() = 100
ar_BH | Arabic (Bahrain)
raj | Rajasthani
ne_NP_#Deva | Nepali (Devanagari, Nepal)
ar_DJ | Arabic (Djibouti)
mzn | Mazanderani
sat__#Olck | Santali (Ol Chiki)
my_MM | Burmese (Myanmar (Burma))
ar_ER | Arabic (Eritrea)
ur_IN | Urdu (India)
ar_EG | Arabic (Egypt)
ks_IN | Kashmiri (India)
ckb | Central Kurdish
sat_IN | Santali (India)
sa_IN_#Deva | Sanskrit (Devanagari, India)
ar_JO | Arabic (Jordan)
pa__#Arab | Punjabi (Arabic)
ne_IN | Nepali (India)
ar_IQ | Arabic (Iraq)
ar_IL | Arabic (Israel)
ar_LB | Arabic (Lebanon)
ar_KW | Arabic (Kuwait)
ar_KM | Arabic (Comoros)
ne_NP | Nepali (Nepal)
dz_BT_#Tibt | Dzongkha (Tibetan, Bhutan)
ar_MR | Arabic (Mauritania)
bn_BD_#Beng | Bangla (Bangla, Bangladesh)
ar_PS | Arabic (Palestinian Territories)
ar_OM | Arabic (Oman)
ckb_IR | Central Kurdish (Iran)
ckb_IQ | Central Kurdish (Iraq)
ar_SA | Arabic (Saudi Arabia)
ar_QA | Arabic (Qatar)
ar_TD | Arabic (Chad)
ar_SO | Arabic (Somalia)
raj_IN_#Deva | Rajasthani (Devanagari, India)
ar_SD | Arabic (Sudan)
ar_SY | Arabic (Syria)
ar_SS | Arabic (South Sudan)
ks_IN_#Arab | Kashmiri (Arabic, India)
nqo_GN | N’Ko (Guinea)
uz__#Arab | Uzbek (Arabic)
lrc_IR_#Arab | Northern Luri (Arabic, Iran)
ar_YE | Arabic (Yemen)
sd_IN | Sindhi (India)
nqo | N’Ko
dz_BT | Dzongkha (Bhutan)
ckb_IQ_#Arab | Central Kurdish (Arabic, Iraq)
lrc | Northern Luri
lrc_IR | Northern Luri (Iran)
lrc_IQ | Northern Luri (Iraq)
sat | Santali
sd__#Arab | Sindhi (Arabic)
th_TH_TH_#u-nu-thai | Thai (Thailand, TH, Thai Digits)
as | Assamese
sd_PK | Sindhi (Pakistan)
mni_IN | Manipuri (India)
bn | Bangla
ks__#Arab | Kashmiri (Arabic)
bgc_IN_#Deva | Haryanvi (Devanagari, India)
dz | Dzongkha
fa | Persian
sat_IN_#Olck | Santali (Ol Chiki, India)
bgc | Haryanvi
bho | Bhojpuri
raj_IN | Rajasthani (India)
as_IN | Assamese (India)
mzn_IR_#Arab | Mazanderani (Arabic, Iran)
uz_AF_#Arab | Uzbek (Arabic, Afghanistan)
ps_AF | Pashto (Afghanistan)
ks | Kashmiri
ar_EG_#Arab | Arabic (Arabic, Egypt)
nqo_GN_#Nkoo | N’Ko (N’Ko, Guinea)
bn_BD | Bangla (Bangladesh)
mr | Marathi
my | Burmese
ne | Nepali
mr_IN | Marathi (India)
ja_JP_JP_#u-ca-japanese | Japanese (Japan, JP, Japanese Calendar)
ps | Pashto
fa_AF | Persian (Afghanistan)
sa | Sanskrit
sd | Sindhi
bho_IN | Bhojpuri (India)
bn_IN | Bangla (India)
ps_AF_#Arab | Pashto (Arabic, Afghanistan)
as_IN_#Beng | Assamese (Bangla, India)
bho_IN_#Deva | Bhojpuri (Devanagari, India)
my_MM_#Mymr | Burmese (Myanmar, Myanmar (Burma))
mni | Manipuri
pa_PK_#Arab | Punjabi (Arabic, Pakistan)
ps_PK | Pashto (Pakistan)
fa_IR | Persian (Iran)
sd_PK_#Arab | Sindhi (Arabic, Pakistan)
mni_IN_#Beng | Manipuri (Bangla, India)
mr_IN_#Deva | Marathi (Devanagari, India)
mzn_IR | Mazanderani (Iran)
mni__#Beng | Manipuri (Bangla)
sa_IN | Sanskrit (India)
fa_IR_#Arab | Persian (Arabic, Iran)
bgc_IN | Haryanvi (India)
« fin »
For more discussion, see this Question by Mahozad.
java.util.Date,java.util.Calendar, andjava.text.SimpleDateFormatare now legacy, supplanted by the java.time classes built into Java 8 & Java 9. See Tutorial by Oracle.