This exercise comes from Horstmann's book Core Java for the impatient:
Write a program that demonstrates the date and time formatting styles in [...] Thailand (with Thai digits).
I tried to solve the exerciese with the following snippet:
Locale locale = Locale.forLanguageTag("th-TH-TH");
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
System.out.println(formatter.withLocale(locale).format(dateTime));
The problem is that while the name of month is given properly in Thai (at least I think so, since I don't know Thai), the numbers are still formatted with Arabic numerals, the output is as follows:
3 ก.ย. 2017, 22:42:16
I tried different language tags ("th-TH", "th-TH-TH", "th-TH-u-nu-thai") to no avail. What should I change to make the program behave as desired? I use JDK 1.8.0_131 on Windows 10 64 bit.
DateTimeFormatter::withDecimalStyle
I was able to solve the exercise. One must pass a DecimalStyle to the formatter by calling DateTimeFormatter::withDecimalStyle, like this (see the code in bold for changes):
Locale locale = Locale.forLanguageTag("th-TH-u-nu-thai");
LocalDateTime dateTime = LocalDateTime.now();
DecimalStyle style = DecimalStyle.of(locale);
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
System.out.println(formatter.withLocale(locale).withDecimalStyle(style).format(dateTime));
Related
Is there a way to format a day and month (in compact form), but not year, in the locale-correct order in Java/Kotlin? So for English it should be "Sep 20" but for Swedish "20 sep.".
For comparison, on Cocoa platforms, I can do the following (in Swift):
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "sv_SE")
formatter.setLocalizedDateFormatFromTemplate("MMM d")
print(formatter.string(from: Date()))
This will correctly turn things around. Is there an equivalent thing to do with the Java SDKs? I've been trying various forms with both DateTimeFormatter and the older SimpleTimeFormat APIs, but no success.
Notes: Unlike this question, I don't want the full medium format that includes the year. I also don't want either DateTimeFormatter.ofPattern("MMM d"), since that gives the incorrect result in Swedish, or DateTimeFormatter.ofPattern("d MMM"), since that gives the incorrect result in English.
No, sorry. I know of no Java library that will automatically turn "MMM d" around into 20 sep. for a locale that prefers the day of month before the month abbreviation.
You may try modifying the answer by Rowi in this way:
DateTimeFormatter ft =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.forLanguageTag("sv-SE"))
;
However the result is:
20 sep. 2019
It includes the year, which you didn’t ask for.
An advanced solution would use the DateTimeFormatterBuilder class to build DateTimeFormatter objects.
DateTimeFormatterBuilder
.getLocalizedDateTimePattern(
FormatStyle.MEDIUM,
null,
IsoChronology.INSTANCE,
Locale.forLanguageTag("sv-SE")
)
This returns d MMM y. Modify this string to delete the y and the space before it. Note that in other languages the y may be yy, yyyy or u and may not come last in the string. Pass your modified format pattern string to DateTimeFormatter.ofPattern.
It may be shaky. Even if you look through the format pattern strings for all available locales, the next version of CLDR (where the strings come from) might still contain a surprise. But I think it’s the best we can do. If it were me, I’d consider throwing an exception in case I can detect that the string from getLocalizedDateTimePattern doesn’t look like one I know how to modify.
You can do it in Java using LocalDate:
LocalDate dt = LocalDate.parse("2019-09-20");
System.out.println(dt);
DateTimeFormatter ft = DateTimeFormatter.ofPattern("dd MMM", new Locale("sv","SE"));
System.out.println(ft.format(dt));
You could get the DateFormat's pattern and remove the year:
val locale: Locale
val datePattern = (DateFormat.getDateInstance(DateFormat.MEDIUM, locale) as SimpleDateFormat).toPattern()
.replace("y", "").trim { it < 'A' || it > 'z' }
After searching, i was unable to find a solution for this problem.
i have this:
Long parseDt = Long.valueOf(arrayJson.getJSONObject(i-j).getInt("dt")); // dt is a timestamp
Locale locale = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()).get(0);
String date = new SimpleDateFormat("EEE d MMM", locale).format(new java.util.Date(parseDt * 1000));
So, the pattern works but when i change the phone language, it didn't swap the way it displays, i want to mean if the phone language is French it displays the date like this and it is good for most of the european language:
lun 2 juil
and if i switch language to English, it displays the date like that:
mon 2 july
instead of:
mon july 2
Do you have any clue to solve this problem respecting my pattern knowing that i want the name of the day with 3 characters maximum?
The existing predefined format (like FULL) could work but the name of the day is displayed entirely, for example it is "monday" and i would like only "mon", not "monday", so, any idea?
You could toggle the format according to the locale you got.
Locale locale = ConfigurationCompat.getLocales(
Resources.getSystem().getConfiguration()).get(0);
String fmt = (locale == Locale.ENGLISH) ? "EEE MMM d" : "EEE d MMM";
String date = new SimpleDateFormat(fmt, locale)
.format(new java.util.Date(parseDt * 1000));
Java already “knows” in which locales the day of month goes before the month name and in which it goes after. This is what the full predefined format gave you. So for a general solution that may work in all or most locales I suggest that you rely on this. We can modify the predefined full pattern to use abbreviations for day of week and for month:
int parseDt = arrayJson.getJSONObject(i-j).getInt("dt"); // dt is a timestamp
Locale locale = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()).get(0);
String formatPattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(
FormatStyle.FULL, null, IsoChronology.INSTANCE, locale);
formatPattern = formatPattern.replaceAll("E{3,}", "EEE").replaceAll("M{3,}", "MMM");
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(formatPattern, locale);
ZoneId zone = ZoneId.of("America/Whitehorse");
ZonedDateTime dateTime = Instant.ofEpochSecond(parseDt).atZone(zone);
String date = dateTime.format(dateFormatter);
System.out.println(date);
Example output:
In UK locale: Mon, 2 Jul 2018
In US locale: Mon, Jul 2, 2018
In FRENCH locale: lun. 2 juil. 2018
I am using regular expressions to make sure that if day of week and/or month is in the format pattern string (for example EEEE and MMMM), it is modified to its abbreviation. Since day of week may also be indicated by lowercase e or c and month by L, you may want to do the same trick with these letters.
I am using java.time, the modern Java date and time API. To use this on not-brand-new Android, get the ThreeTenABP library: use the links at the bottom. If you object to using a high-quality future-proof external library, you may be able to play a similar trick with SimpleDateFormat.
If you want to omit the year too (as in your examples), it is slightly more complicated, but doable. Here’s a simple attempt to remove the year if it comes last:
formatPattern = formatPattern.replaceFirst("^(.*[\\w'])([^\\w']*y+)$", "$1");
With this line inserted the output in French locale is just lun. 2 juil., for example. Year may also be given with letter u, you may want to take this into account too.
Converting your Unix time to a date is a time zone sensitive operation, so I give time zone in the code. Please put the one you desire where I put America/Whitehorse. If you trust that the JVM’s time zone setting reflects what the user wants, you may use ZoneId.systemDefault() (the SimpleDateFormat in your own code uses the VM setting too).
Links
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.timeto Java 6 and 7 (ThreeTen for JSR-310).
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
According to the date format of the countries:
https://en.wikipedia.org/wiki/Date_format_by_country
and my app needs, I've choosen the easiest way to display it:
int parseDt = arrayJson.getJSONObject(i-j).getInt("dt"); // dt is a timestamp
Locale locale = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()).get(0);
String dateFormat = ((locale.equals(Locale.US) || locale.equals(Locale.CHINA) || locale.equals(Locale.CANADA) || locale.equals(Locale.JAPAN)) ? "EEE, MMM d" : "EEE d MMM";
String date = new SimpleDateFormat(dateFormat, locale).format(new java.util.Date(parseDt * 1000));
I am trying to parse a date into an appropriate format, but I keep getting the error
Unparseable date
Can anyone tell me what the mistake is?
try {
System.out.println(new SimpleDateFormat("d-MMM-Y").parse("05-03-2018").toString());
} catch (ParseException e) {
e.printStackTrace();
}
I want the date to have this format:
05-Mar-18
Since you want to change the format, first read and parse the date (from String) of your own format in a Date type object. Then use that date object by formatting it into a new (desired) format using a SimpleDateFormat.
The error in your code is with the MMM and Y. MMM is the month in string while your input is a numeric value. Plus the Y in your SimpleDateFormat is an invalid year. yy is what needs to be added.
So here is a code that would fix your problem.
SimpleDateFormat dateFormat = new SimpleDateFormat("d-MM-yyyy");
Date date = dateFormat.parse("05-03-2018");
dateFormat = new SimpleDateFormat("dd-MMM-yy");
System.out.println(dateFormat.format(date));
I hope this is what you're looking for.
There are some concepts about dates you should be aware of.
There's a difference between a date and a text that represents a date.
Example: today's date is March 9th 2018. That date is just a concept, an idea of "a specific point in our calendar system".
The same date, though, can be represented in many formats. It can be "graphical", in the form of a circle around a number in a piece of paper with lots of other numbers in some specific order, or it can be in plain text, such as:
09/03/2018 (day/month/year)
03/09/2018 (monty/day/year)
2018-03-09 (ISO8601 format)
March, 9th 2018
9 de março de 2018 (in Portuguese)
2018年3月5日 (in Japanese)
and so on...
Note that the text representations are different, but all of them represent the same date (the same value).
With that in mind, let's see how Java works with these concepts.
a text is represented by a String. This class contains a sequence of characters, nothing more. These characters can represent anything; in this case, it's a date
a date was initially represented by java.util.Date, and then by java.util.Calendar, but those classes are full of problems and you should avoid them if possible. Today we have a better API for that.
With the java.time API (or the respective backport for versions lower than 8), you have easier and more reliable tools to deal with dates.
In your case, you have a String (a text representing a date) and you want to convert it to another format. You must do it in 2 steps:
convert the String to some date-type (transform the text to numerical day/month/year values) - that's called parsing
convert this date-type value to some format (transform the numerical values to text in a specific format) - that's called formatting
For step 1, you can use a LocalDate, a type that represents a date (day, month and year, without hours and without timezone), because that's what your input is:
String input = "05-03-2018";
DateTimeFormatter inputParser = DateTimeFormatter.ofPattern("dd-MM-yyyy");
// parse the input
LocalDate date = LocalDate.parse(input, inputParser);
That's more reliable than SimpleDateFormat because it solves lots of strange bugs and problems of the old API.
Now that we have our LocalDate object, we can do step 2:
// convert to another format
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy", Locale.ENGLISH);
String output = date.format(formatter);
Note that I used a java.util.Locale. That's because the output you want has a month name in English, and if you don't specify a locale, it'll use the JVM's default (and who guarantees it'll always be English? it's better to tell the API which language you're using instead of relying on the default configs, because those can be changed anytime, even by other applications running in the same JVM).
And how do I know which letters must be used in DateTimeFormatter? Well, I've just read the javadoc. Many developers ignore the documentation, but we must create the habit to check it, specially the javadoc, that tells you things like the difference between uppercase Y and lowercase y in SimpleDateFormat.
It is quite easy to format and parse Java Date (or Calendar) classes using instances of DateFormat.
I could format the current date into a short localized date like this:
DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
String today = formatter.format(new Date());
My problem is that I need to obtain this localized pattern string (something like "MM/dd/yy").
This should be a trivial task, but I just couldn't find the provider.
For SimpleDateFormat, You call toLocalizedPattern()
EDIT:
For Java 8 users:
The Java 8 Date Time API is similar to Joda-time. To gain a localized pattern we can use class
DateTimeFormatter
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
Note that when you call toString() on LocalDate, you will get date in format ISO-8601
Note that Date Time API in Java 8 is inspired by Joda Time and most solution can be based on questions related to time.
For those still using Java 7 and older:
You can use something like this:
DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
String pattern = ((SimpleDateFormat)formatter).toPattern();
String localPattern = ((SimpleDateFormat)formatter).toLocalizedPattern();
Since the DateFormat returned From getDateInstance() is instance of SimpleDateFormat.
Those two methods should really be in the DateFormat too for this to be less hacky, but they currently are not.
It may be strange, that I am answering my own question, but I believe, I can add something to the picture.
ICU implementation
Obviously, Java 8 gives you a lot, but there is also something else: ICU4J. This is actually the source of Java original implementation of things like Calendar, DateFormat and SimpleDateFormat, to name a few.
Therefore, it should not be a surprise that ICU's SimpleDateFormat also contains methods like toPattern() or toLocalizedPattern(). You can see them in action here:
DateFormat fmt = DateFormat.getPatternInstance(
DateFormat.YEAR_MONTH,
Locale.forLanguageTag("pl-PL"));
if (fmt instanceof SimpleDateFormat) {
SimpleDateFormat sfmt = (SimpleDateFormat) fmt;
String pattern = sfmt.toPattern();
String localizedPattern = sfmt.toLocalizedPattern();
System.out.println(pattern);
System.out.println(localizedPattern);
}
ICU enhancements
This is nothing new, but what I really wanted to point out is this:
DateFormat.getPatternInstance(String pattern, Locale locale);
This is a method that can return a whole bunch of locale specific patterns, such as:
ABBR_QUARTER
QUARTER
YEAR
YEAR_ABBR_QUARTER
YEAR_QUARTER
YEAR_ABBR_MONTH
YEAR_MONTH
YEAR_NUM_MONTH
YEAR_ABBR_MONTH_DAY
YEAR_NUM_MONTH_DAY
YEAR_MONTH_DAY
YEAR_ABBR_MONTH_WEEKDAY_DAY
YEAR_MONTH_WEEKDAY_DAY
YEAR_NUM_MONTH_WEEKDAY_DAY
ABBR_MONTH
MONTH
NUM_MONTH
ABBR_STANDALONE_MONTH
STANDALONE_MONTH
ABBR_MONTH_DAY
MONTH_DAY
NUM_MONTH_DAY
ABBR_MONTH_WEEKDAY_DAY
MONTH_WEEKDAY_DAY
NUM_MONTH_WEEKDAY_DAY
DAY
ABBR_WEEKDAY
WEEKDAY
HOUR
HOUR24
HOUR_MINUTE
HOUR_MINUTE_SECOND
HOUR24_MINUTE
HOUR24_MINUTE_SECOND
HOUR_TZ
HOUR_GENERIC_TZ
HOUR_MINUTE_TZ
HOUR_MINUTE_GENERIC_TZ
MINUTE
MINUTE_SECOND
SECOND
ABBR_UTC_TZ
ABBR_SPECIFIC_TZ
SPECIFIC_TZ
ABBR_GENERIC_TZ
GENERIC_TZ
LOCATION_TZ
Sure, there are quite a few. What is good about them, is that these patterns are actually strings (as in java.lang.String), that is if you use English pattern "MM/d", you'll get locale-specific pattern in return. It might be useful in some corner cases. Usually you would just use DateFormat instance, and won't care about the pattern itself.
Locale-specific pattern vs. localized pattern
The question intention was to get localized, and not the locale-specific pattern. What's the difference?
In theory, toPattern() will give you locale-specific pattern (depending on Locale you used to instantiate (Simple)DateFormat). That is, no matter what target language/country you put, you'll get the pattern composed of symbols like y, M, d, h, H, M, etc.
On the other hand, toLocalizedPattern() should return localized pattern, that is something that is suitable for end users to read and understand. For instance, German middle (default) date pattern would be:
toPattern(): dd.MM.yyyy
toLocalizedPattern(): tt.MM.jjjj (day = Tag, month = Monat, year = Jahr)
The intention of the question was: "how to find the localized pattern that could serve as hint as to what the date/time format is". That is, say we have a date field that user can fill-out using the locale-specific pattern, but I want to display a format hint in the localized form.
Sadly, so far there is no good solution. The ICU I mentioned earlier in this post, partially works. That's because, the data that ICU uses come from CLDR, which is unfortunately partially translated/partially correct. In case of my mother's tongue, at the time of writing, neither patterns, nor their localized forms are correctly translated. And every time I correct them, I got outvoted by other people, who do not necessary live in Poland, nor speak Polish language...
The moral of this story: do not fully rely on CLDR. You still need to have local auditors/linguistic reviewers.
You can use DateTimeFormatterBuilder in Java 8. Following example returns localized date only pattern e.g. "d.M.yyyy".
String datePattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(
FormatStyle.SHORT, null, IsoChronology.INSTANCE,
Locale.GERMANY); // or whatever Locale
The following code will give you the pattern for the locale:
final String pattern1 = ((SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, locale)).toPattern();
System.out.println(pattern1);
Java 8 provides some useful features out of the box for working with and formatting/parsing date and time, including handling locales. Here is a brief introduction.
Basic Patterns
In the simplest case to format/parse a date you would use the following code with a String pattern:
DateTimeFormatter.ofPattern("MM/dd/yyyy")
The standard is then to use this with the date object directly for formatting:
return LocalDate.now().format(DateTimeFormatter.ofPattern("MM/dd/yyyy"));
And then using the factory pattern to parse a date:
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern("MM/dd/yyyy"));
The pattern itself has a large number of options that will cover the majority of usecases, a full rundown can be found at the javadoc location here.
Locales
Inclusion of a Locale is fairly simple, for the default locale you have the following options that can then be applied to the format/parse options demonstrated above:
DateTimeFormatter.ofLocalizedDate(dateStyle);
The 'dateStyle' above is a FormatStyle option Enum to represent the full, long, medium and short versions of the localized Date when working with the DateTimeFormatter. Using FormatStyle you also have the following options:
DateTimeFormatter.ofLocalizedTime(timeStyle);
DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle);
DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle, timeStyle);
The last option allows you to specify a different FormatStyle for the date and the time. If you are not working with the default Locale the return of each of the Localized methods can be adjusted using the .withLocale option e.g
DateTimeFormatter.ofLocalizedTime(timeStyle).withLocale(Locale.ENGLISH);
Alternatively the ofPattern has an overloaded version to specify the locale too
DateTimeFormatter.ofPattern("MM/dd/yyyy",Locale.ENGLISH);
I Need More!
DateTimeFormatter will meet the majority of use cases, however it is built on the DateTimeFormatterBuilder which provides a massive range of options to the user of the builder. Use DateTimeFormatter to start with and if you need these extensive formatting features fall back to the builder.
Please find in the below code which accepts the locale instance and returns the locale specific data format/pattern.
public static String getLocaleDatePattern(Locale locale) {
// Validating if Locale instance is null
if (locale == null || locale.getLanguage() == null) {
return "MM/dd/yyyy";
}
// Fetching the locale specific date pattern
String localeDatePattern = ((SimpleDateFormat) DateFormat.getDateInstance(
DateFormat.SHORT, locale)).toPattern();
// Validating if locale type is having language code for Chinese and country
// code for (Hong Kong) with Date Format as - yy'?'M'?'d'?'
if (locale.toString().equalsIgnoreCase("zh_hk")) {
// Expected application Date Format for Chinese (Hong Kong) locale type
return "yyyy'MM'dd";
}
// Replacing all d|m|y OR Gy with dd|MM|yyyy as per the locale date pattern
localeDatePattern = localeDatePattern.replaceAll("d{1,2}", "dd").replaceAll(
"M{1,2}", "MM").replaceAll("y{1,4}|Gy", "yyyy");
// Replacing all blank spaces in the locale date pattern
localeDatePattern = localeDatePattern.replace(" ", "");
// Validating the date pattern length to remove any extract characters
if (localeDatePattern.length() > 10) {
// Keeping the standard length as expected by the application
localeDatePattern = localeDatePattern.substring(0, 10);
}
return localeDatePattern;
}
Since it's just the locale information you're after, I think what you'll have to do is locate the file which the JVM (OpenJDK or Harmony) actually uses as input to the whole Locale thing and figure out how to parse it. Or just use another source on the web (surely there's a list somewhere). That'll save those poor translators.
You can try something like :
LocalDate fromCustomPattern = LocalDate.parse("20.01.2014", DateTimeFormatter.ofPattern("MM/dd/yy"))
Im not sure about what you want, but...
SimpleDateFormat example:
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yy");
Date date = sdf.parse("12/31/10");
String str = sdf.format(new Date());
In java how do I display dates in different locales (for e.g. Russian).
Something like:
Locale locale = new Locale("ru","RU");
DateFormat full = DateFormat.getDateInstance(DateFormat.LONG, locale);
out.println(full.format(new Date()));
Should do the trick. However, there was a problem of Russian Date formatting in jdk1.5
The deal with Russian language is that month names have different suffix when they are presented stand-alone (i.e. in a list or something) and yet another one when they are part of a formatted date. So, even though March is "Март" in Russian, correctly formatted today's date would be: "7 Марта 2007 г."
Let's see how JDK formats today's date: 7 Март 2007 г. Clearly wrong.
Use SimpleDateFormat constructor which takes locale. You need to first check if JDK supports the locale you are looking for, if not then you need to implement that.
Use the java.text.DateFormat class, you can construct that's configured to a specific Locale.
DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, theLocaleYouWant);
String text = format.format(new Date());
System.out.println(text);
The DateFormat class can help you. As explained in the Javadoc:
To format a date for a different
Locale, specify it in the call to
getDateInstance().
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
So you just need to adapt this code by using the adequate Locale.
Use a java.util.Calendar with an appropriate time zone and locale.