This question already has answers here:
How to format YearMonth and MonthDay depending on a Locale?
(3 answers)
Closed 5 years ago.
I am trying to format a MonthDay object in a way that I do not have to specify the order. I am trying to use a localized DateTimeFormatter.
I have this code:
LocalDate datetime = LocalDate.parse("2017-08-11", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
MonthDay monthday = MonthDay.from(datetime);
System.out.println(monthday.format(DateTimeFormatter.ofPattern("MMMM dd").withLocale(Locale.ENGLISH)));
System.out.println(monthday.format(DateTimeFormatter.ofPattern("MMMM dd").withLocale(Locale.GERMANY)));
System.out.println(monthday.format(DateTimeFormatter.ofPattern("MMMM dd").withLocale(Locale.forLanguageTag("UK"))));
System.out.println(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.ENGLISH)));
System.out.println(datetime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"))));
// next line throws exception for java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra
System.out.println(monthday.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"))));
The first 3 prints will print as expected the translated Month and day, but it is always month and then day. It does not change the order because I am explicitly telling it the order.
The next two (before the exception) would print respectively:
Aug 11, 2017
11 серп. 2017
Notice how the day is either before or after the month depending on the locale passed to the function. How do I do this with a MonthDay object as the last line throws an exception when done in this way.
The other answers given so far describe the limitations of the standard DateTimeFormatter, see also the unsolved related JDK-issue. The suggested workaround to edit the localized date pattern by removing "y" etc. is tricky and might not work for all locales due to the existence of other localized literals inside the pattern.
However, you might also consider using external libraries which have a stronger focus on internationalization issues and have the capability to format a month-day-object using just the locale information. So the locale determines the order of field components and also dots, spaces or other special literals (like in Chinese).
Here two options with the necessary type conversions related to your system timezone:
ICU4J
MonthDay md = MonthDay.now();
GregorianCalendar gcal =
new GregorianCalendar(
2000, // avoids possible leap year problems
md.getMonthValue() - 1,
md.getDayOfMonth()
);
DateFormat df =
DateFormat.getInstanceForSkeleton(
DateFormat.ABBR_MONTH_DAY,
Locale.forLanguageTag("en")
);
System.out.println(df.format(gcal.getTime())); // Aug 15
DateFormat df2 =
DateFormat.getInstanceForSkeleton(
DateFormat.ABBR_MONTH_DAY,
Locale.forLanguageTag("de")
);
System.out.println(df2.format(gcal.getTime())); // 15. Aug.
DateFormat df3 =
DateFormat.getInstanceForSkeleton(DateFormat.MONTH_DAY, Locale.forLanguageTag("zh"));
System.out.println(df3.format(gcal.getTime())); // 8月15日
Time4J
MonthDay md = MonthDay.now();
ChronoFormatter<AnnualDate> cf1 =
ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.GERMAN, AnnualDate.chronology());
System.out.println(cf1.format(AnnualDate.from(md))); // 15.8.
ChronoFormatter<AnnualDate> cf2 =
ChronoFormatter.ofStyle(DisplayMode.MEDIUM, Locale.GERMAN, AnnualDate.chronology());
System.out.println(cf2.format(AnnualDate.from(md))); // 15.08.
ChronoFormatter<AnnualDate> cf3 =
ChronoFormatter.ofStyle(DisplayMode.LONG, Locale.ENGLISH, AnnualDate.chronology());
System.out.println(cf3.format(AnnualDate.from(md))); // Aug 15
ChronoFormatter<AnnualDate> cf4 =
ChronoFormatter.ofStyle(DisplayMode.FULL, Locale.GERMAN, AnnualDate.chronology());
System.out.println(cf4.format(AnnualDate.from(md))); // 15. August
ChronoFormatter<AnnualDate> cf5 =
ChronoFormatter.ofStyle(DisplayMode.FULL, Locale.CHINESE, AnnualDate.chronology());
System.out.println(cf5.format(AnnualDate.from(md))); // 8月15日
Disclaimer: Time4J has been written by myself to fill gaps or to improve other features of JSR-310 (java.time-package).
MonthDay does not store the year information and the FormatStyle.MEDIUM requires a year value, thats why the formatter is not finding the field YearOfEra same occurs if you use YearMonth with same FormatStyle but now for the missing field DayOfMonth.
MonthDay API
This class does not store or represent a year, time or time-zone. For example, the value "December 3rd" can be stored in a MonthDay.
You could transform monthday to an Instant or simply use the datetime.
ofLocalizedDate returns a formatter for the date, so it formats the day, month and year fields, so the object being formatted needs to have all the three fields. MonthDay doesn't have the year field, that's why it throws a UnsupportedTemporalTypeException.
If you want to print the whole date (with day, month and year), you must add the year to the MonthDay object. You can use the atYear method for that:
System.out.println(monthday.atYear(2017).format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"))));
This will output:
11 серп. 2017
If you want the current year, just use Year.now().getValue() instead of 2017.
You can also use datetime.getYear(), if you want the same year of the LocalDate - or use the datetime instead of the monthday.
If you want to print just the day and month, you'll have to do some workarounds.
As the localized formatters are built-in in the JDK (and there seems to be no way to change them), you don't have a direct way of doing it, though there are some alternatives.
One (ugly) solution is to set a year and then remove it from the formatted String:
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.forLanguageTag("UK"));
// set year to 2017, then remove "2017" from the formatted string
System.out.println(monthday.atYear(2017).format(formatter).replace("2017", "").trim());
The output is:
11 серп.
The boring part is to remove all extra characters that might exist in each locale. For the English locale, I also had to remove the ,:
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.ENGLISH);
// remove the year (2017) and the "," from the output
System.out.println(monthday.atYear(2017).format(formatter).replace("2017", "").replace(",", "").trim());
The output is:
Aug 11
You'll have to check all the extra characters (such as ,) for all locales, and remove them from the output. Or just use ofPattern with fixed non-locale specific patterns (as you did in your first 3 tests).
As #BasilBourque's noticed in the comments, I'm assuming that the year is at the beggining or end of the pattern. If the year is in the middle, there will be some extra spaces in the final result, which can be removed with .replaceAll("\\s{2,}", " ") (2 or more spaces are replaced by just one).
Another alternative (as suggested by #JodaStephen comment) is to use a DateTimeFormatterBuilder to get the localized date pattern.
Then I remove the year from the pattern, replacing y and u (the patterns used for the year), and also remove some other characters (like , and extra spaces).
With the resulting pattern (without the year), I create a DateTimeFormatter with the specified locale and format the MonthDay:
// get date pattern for the specified locale
String pattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.forLanguageTag("UK"));
pattern = pattern
// remove the year (1 or more occurrences of "y" or "u")
.replaceAll("[yu]+", "")
// replace "," (you can change this to remove any other characters you want)
.replaceAll(",", "")
// replace 2 or more spaces with just one space and trim to remove spaces in the start or end
.replaceAll("\\s{2,}", " ").trim();
// create formatter for the pattern and locale
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern, Locale.forLanguageTag("UK"));
System.out.println(monthday.format(fmt));
The output will be:
11 серп.
Just reminding that this example might be incomplete, because there are some locales that uses /, - and other characters as separators and you must remove them from the final result. Check all the locales you're working with and remove the characters accordingly.
Another corner-case not covered by this is when you have y or u as literals (inside '): in this case they shouldn't be removed. Anyway, you'll have to check the formats for all locales you're working with and handle each case accordingly.
Related
I'm using Joda-Time to parse years like this:
private DateTime attemptParse(String pattern, String date) {
DateTimeFormatter parser = DateTimeFormat.forPattern(pattern).withLocale(Locale.ENGLISH);
DateTime parsedDateTime = parser.parseLocalDateTime(date).toDateTime(WET);
return parsedDateTime;
}
I'm trying to parse multiple formats: "yyyy-MM-dd", "yyyy-MMM-dd","yyyy MMM dd-dd","yyyy MMM", (etc), "yyyy". When one doesn't work, I try the next one.
And it works like a charm when the string is indeed only 4 digits (e.g: "2016"). The problem is that I sometimes receive things like this: "201400". And Joda-Time matches this with "yyyy" pattern and returns a date with year 201400.
I wanted to avoid the ugly if to check if year > 9999. Is there any way to do this using Joda-Time?
To parse multiple formats, you can create lots of DateTimeParser instances and join all in one single formatter (instead of trying one after another).
This will require a DateTimeFormatterBuilder, which will also be used to enforce a specific number of digits in the input (unfortunately, there's no way to enforce a specific number of digits like you want using just DateTimeFormat.forPattern()).
First you create lots of org.joda.time.format.DateTimeParser instances (one for each possible pattern):
// only yyyy
DateTimeParser p1 = new DateTimeFormatterBuilder()
// year with exactly 4 digits
.appendYear(4, 4).toParser();
// yyyy-MM-dd
DateTimeParser p2 = new DateTimeFormatterBuilder()
// year with exactly 4 digits
.appendYear(4, 4)
// rest of the pattern
.appendPattern("-MM-dd").toParser();
// yyyy MMM
DateTimeParser p3 = new DateTimeFormatterBuilder()
// year with exactly 4 digits
.appendYear(4, 4)
// rest of the pattern
.appendPattern(" MMM").toParser();
Then you create an array with all these patterns and create a DateTimeFormatter with it:
// create array with all the possible patterns
DateTimeParser[] possiblePatterns = new DateTimeParser[] { p1, p2, p3 };
DateTimeFormatter parser = new DateTimeFormatterBuilder()
// append all the possible patterns
.append(null, possiblePatterns)
// use the locale you want (in case of month names and other locale sensitive data)
.toFormatter().withLocale(Locale.ENGLISH);
I also used Locale.ENGLISH (as you're also using it in your question's code). This locale indicates that the month names will be in English (so MMM can parse values like Jan and Sep). With this, you can parse the inputs:
System.out.println(parser.parseLocalDateTime("2014")); // OK
System.out.println(parser.parseLocalDateTime("201400")); // exception
System.out.println(parser.parseLocalDateTime("2014-10-10")); // OK
System.out.println(parser.parseLocalDateTime("201400-10-10")); // exception
System.out.println(parser.parseLocalDateTime("2014 Jul")); // OK
System.out.println(parser.parseLocalDateTime("201400 Jul")); // exception
When the year is 2014, the code works fine. When it's 201400, it throws a java.lang.IllegalArgumentException, such as:
java.lang.IllegalArgumentException: Invalid format: "201400" is malformed at "00"
DateTimeFormatter is immutable and thread-safe, so you don't need to create it every time your validation method is called. You can create it outside of the method (such as in a static final field).
This is better than creating one formatter everytime you perform a validation, and going to the next one when an exception occurs. The formatter created already does it internally, going to the next pattern until it finds one that works (or throwing the exception if all patterns fail).
Java new Date/Time API
Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".
If you can't (or don't want to) migrate from Joda-Time to the new API, you can ignore this section.
If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.
If you're using Java 6 or 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP (more on how to use it here).
The code below works for both.
The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.
This new API is much more strict than the previous ones, so the formatter only works with the exact number of digits (note that some classes are very similar to Joda-Time):
// 4 digits in year
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy", Locale.ENGLISH);
fmt.parse("2014"); // OK
fmt.parse("201400"); // exception
fmt.parse("201"); // exception
This code works with 2014, but with 201400 or 201 (or any other value without exactly 4 digits) it throws an exception:
java.time.format.DateTimeParseException: Text '201400' could not be parsed at index 0
With this, your validation code could work with the array of strings.
There's only one detail: when parsing to a date, Joda-Time sets default values when the input doesn't have some fields (like month becomes January, day becomes 1, hour/minute/second are set to zero, etc).
If you are just validating the input, then you don't need to return anything. Just check if the exception is thrown and you'll know if the input is valid or not.
If you just need the year value, though, you can use the Year class:
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy", Locale.ENGLISH);
System.out.println(Year.parse("2014", parser)); // ok
System.out.println(Year.parse("201400", parser)); // exception
If you want the year value as an int:
Year year = Year.parse("2014", parser);
int yearValue = year.getValue(); // 2014
But if you want to get a date object, you'll need to set the default values manually - the new API is very strict and don't set those values automatically. In this case, you must set the default values, by using a DateTimeFormatterBuilder.
I also parse it to a LocalDateTime, just as example:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// string pattern
.appendPattern("yyyy")
// default month is January
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
// default day is 1
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
// default hour is zero
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
// default minute is zero
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
// set locale
.toFormatter(Locale.ENGLISH);
// create LocalDateTime
System.out.println(LocalDateTime.parse("2014", fmt)); // 2014-01-01T00:00
System.out.println(LocalDateTime.parse("201400", fmt)); // exception
You can choose whatever values you want as the default for the fields, and use any of the new available date types.
What you are saying is that Jodatime should somehow guess that it should parse "201400" as 2014. I don't think that's reasonably within the scope of that library. You should pre-process the data yourself, for example by using:
String normalizedDate = String.format("%4s", date).trim();
Actually, I found problem with MM/dd/yy date format :
If enter year greater than 37 then the year format reflects as 1937.
i.e, if I enter input as 02/05/37 then when I am printing this date into console the date changes into 02/05/1937.
if he entered less than 02/05/37 then working fine.
Date startDate = new SimpleDateFormat("dd/MM/yy").parse("01/01/47");
System.out.println(startDate);
Assuming you're using SimpleDateFormat: It's conform specifications that 02/05/37 is parsed as 02/05/1937. At least for the next year or so...
Java's SimpleDateFormat has to decide in which century your date should be. It does this by adjusting dates to be within 80 years before and 20 years after the time the SimpleDateFormat instance is created. 2037 is within 80 years before the current date (2016), so it uses a time in the past.
The other answers are correct. You need to understand SimpleDateFormat behavior for assuming your intended century.
You are using old outmoded classes. The new recommended classes have a different behavior on this issue.
java.time
The java.time framework built into Java 8 and later supplants the old java.util.Date & SimpleDateFormat classes.
The behavior about assuming century is different. In the DateTimeFormatter class, a two-digit century is interpreted as being in the 21st century, resulting in a year within the range 2000 to 2099 inclusive.
The java.time classes include LocalDate for representing a date-only value without time-of-day and without time zone.
String input = "02/01/47";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "dd/MM/yy" );
LocalDate localDate = LocalDate.parse( input , formatter );
2047-01-02
By the way, a tip: Avoid two-digit years if at all possible. The confusion and trouble induced is not worth the savings of two bytes and a smudge of toner.
If you don't supply century info, then it has to make an assumption and it quite reasonably assumes that you are going to want mostly dates in the past, with some scope for future dates, but not too far, as it's more likely that you'll want prior dates, such as birth dates, etc. And people quite commonly live up to about 80 years of age. So far more dates will be in the past for any given current date, than future ones, based on this assumption.
From the spec...
For parsing with the abbreviated year pattern ("y" or "yy"),
SimpleDateFormat must interpret the abbreviated year relative to some
century. It does this by adjusting dates to be within 80 years before
and 20 years after the time the SimpleDateFormat instance is created.
For example, using a pattern of "MM/dd/yy" and a SimpleDateFormat
instance created on Jan 1, 1997, the string "01/11/12" would be
interpreted as Jan 11, 2012 while the string "05/04/64" would be
interpreted as May 4, 1964. During parsing, only strings consisting of
exactly two digits, as defined by Character.isDigit(char), will be
parsed into the default century. Any other numeric string, such as a
one digit string, a three or more digit string, or a two digit string
that isn't all digits (for example, "-1"), is interpreted literally.
So "01/02/3" or "01/02/003" are parsed, using the same pattern, as Jan
2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
Otherwise, calendar system specific forms are applied. For both
formatting and parsing, if the number of pattern letters is 4 or more,
a calendar specific long form is used. Otherwise, a calendar specific
short or abbreviated form is used.
So, if you to do something with this, then you'll need to check if the formatted date is prior to today's date (or some other cut off that you choose) and just add 100 years to the given date, if you wish to only have future dates or beyond a different cut off from the default one.
In old java I can do it in that way:
System.out.println(new SimpleDateFormat("yyyy w", Locale.UK).parse("2015 1"));
// shows Mon Dec 29 00:00:00 CET 2014
System.out.println(new SimpleDateFormat("yyyy w", Locale.US).parse("2015 1"));
// shows Mon Dec 28 00:00:00 CET 2014
I would like to use java.time in Java 8.
System.out.println( LocalDate.parse("2015 1", DateTimeFormatter.ofPattern("yyyy w", Locale.US)));
Result:
java.time.format.DateTimeParseException: Text '2015 1' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, Year=2015},ISO of type java.time.format.Parsed
How to do it in java.time?
Moreover, I'm not satisfied that I have to pass Locale to determine first day of week: Monday vs Sunday. It is not country feature but calendar feature. I would like to use something like java.time.temporal.WeekFields.ISO to show the world that week start with Monday
I found similar case : https://stackoverflow.com/questions/3941700/how-to-get-dates-of-a-week-i-know-week-number
but not for java.time in Java 8. Moreover, solution that first create a date object and later set correct week is not elegant. I want to create final date in one shot.
Direct answer and solution:
System.out.println(
LocalDate.parse("2015 1",
new DateTimeFormatterBuilder().appendPattern("YYYY w")
.parseDefaulting(WeekFields.ISO.dayOfWeek(), 1)
.toFormatter()));
// output: 2014-12-29
Explanations:
a) You should use Y instead of y because you are interested in ISO-8601-week-date, not in year-of-era.
b) A calendar date cannot be formed by just giving a (week-based) year and a week-number. The day of week matters to determine the day within the specified calendar week. The predefined formatter for week-dates requires the missing day-of-week. So you need to construct a specialized parser using the builder-pattern. Then it is necessary to tell the parser what day of week is wanted - via the method parseDefaulting().
c) I insist (and defend JSR-310 here) on saying that the question when a week starts is not a calendar issue but a country-dependent issue. US and France (as example) use the same calendar but have different views how to define a week. ISO-8601-standard can be applied using the explicitly ISO-referring field WeekFields.ISO.dayOfWeek(). Attention: Testing has revealed that using ChronoField.DAY_OF_WEEK together with Locale.ROOT does not always seem to guarantee ISO-week-behaviour as indicated in my first version of this answer (the reasons are not yet clear for me - a close view of the sources seems to be necessary to enlighten the unintuitive behaviour).
d) The java-time-package does it well - with the exception that Monday is just specified as number 1. I would have preferred the enum. Or use the enum and its method getValue().
e) Side notice: SimpleDateFormat behaves leniently by default. The java-time-package is stricter and rejects to invent a missing day-of-week out of thin air - even in lenient mode (which is in my opinion rather a good thing). Software should not guess so much, instead the programmer should think more about what day-of-week is the right one. Again here: The application requirements will probably differ in US and France about the right default setting.
The accepted Answer by Meno Hochschild is correct. Here is an alternate route.
YearWeek
Use the YearWeek class in ThreeTen-Extra library. This library extends java.time with additional functionality. This particular class represents a year-week in the ISO 8601 week date system.
The factory method YearWeek.of takes a pair of integer arguments, the week-based-year and the week number.
YearWeek yw = YearWeek.of( 2015 , 1 );
ISO 8601
Ideally you would be using standard ISO 8601 formats for strings representing date-time values. For year-week that would be yyyy-Www the week-based-year number, a hyphen, a W, and two digits for week number with padding zero as needed.
2015-W01
The java.time classes, and ThreeTen-Extra classes, use the standard ISO 8601 formats by default when parsing and generating strings. So life is much easier if you stick to the standard.
YearWeek yw = YearWeek.parse( "2015-W01" );
String output = yw.toString(); // 2015-W01
Parsing integers.
You have a non-standard format for numbers. So let's parse your string as two pieces, one number each, to be interpreted as int integers.
String string = "2015 1";
String[] parts = string.split(" "); // SPACE character.
String part1 = parts[0]; // 2015
String part2 = parts[1]; // 1
The Integer class converts such strings to int primitive values.
int weekBasedYearNumber = Integer.parseInt( part1 ) ;
int weekNumber = Integer.parseInt( part2 ) ;
Now call that factory method.
YearWeek yw = YearWeek.of( weekBasedYearNumber , weekNumber );
LocalDate
As for first day of week, here we have been discussing the standard ISO 8601 definition of week. In that definition, Monday is always the first day, a week running from Monday-Sunday. Week # 1 contains the first Thursday of the calendar-year. In the case of 2015-W01 that Thursday would be January 1, 2015.
So, no Locale needed.
I am not quite sure of your goal, but it seems to be extracting particular dates for days within your week. That is quite easy with the YearWeek class and the DayOfWeek enum.
LocalDate monday = yw.atDay( DayOfWeek.MONDAY ); // 2014-12-29 start-of-week.
LocalDate friday = yw.atDay( DayOfWeek.FRIDAY ); // 2015-01-02
LocalDate sunday = yw.atDay( DayOfWeek.SUNDAY ); // 2015-01-04 end-of-week.
This can also be achieved by putting default parse value for Day of week using "ChronoField.Day_Of_Week" and setting the value as "1" as follows:
System.out.println( "Date From Year and Week : "+
LocalDate.parse("2015 1",
new DateTimeFormatterBuilder().appendPattern("YYYY w")
.parseDefaulting(ChronoField.DAY_OF_WEEK, 1)
.toFormatter()));
Is there an equivalent to php date() style formatting in Java? I mean, in php I can backslash-escape characters to have them treated literally. I.e. yyyy \y\e\a\r would become 2010 year. I did not find anything similar in Java, all examples deal only with built-in date formats.
In particular, I deal with JCalendar date pickers and their dateFormatString property.
I need it because in my locale it is required to write all sorts of additional stuff in date format, like d. (for day) after days part, m. (for years) after years part and so on.
At the worst case I could use string replace or regexp but maybe there's a simpler way?
Sure, with the SimpleDateFormat you can include literal strings:
Within date and time pattern strings, unquoted letters from 'A' to 'Z' and from 'a' to 'z' are interpreted as pattern letters representing the components of a date or time string. Text can be quoted using single quotes (') to avoid interpretation. "''" represents a single quote. All other characters are not interpreted; they're simply copied into the output string during formatting or matched against the input string during parsing.
"hh 'o''clock' a, zzzz" 12 o'clock PM, Pacific Daylight Time
Just for completeness, Java 8's DateTimeFormatter also supports this:
DateTimeFormatter.ofPattern("yyyy 'year'");
java.time
Mark Jeronimus said it already. I am fleshing it out a bit more. Just put the text to be printed literally inside single quotes.
DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("yyyy 'year'");
System.out.println(LocalDate.of(2010, Month.FEBRUARY, 3).format(yearFormatter));
System.out.println(Year.of(2010).format(yearFormatter));
System.out.println(ZonedDateTime.now(ZoneId.of("Europe/Vilnius")).format(yearFormatter));
Output when running just now:
2010 year
2010 year
2019 year
If you are using a DateTimeFormatterBuilder and its appendPattern method, use single quotes in the same way. Or use its appendLiteral method instead and no single quotes.
How do we put a single quote in the format, then? Two single quotes produce one. It doesn’t matter if the double single quote is inside single quotes or not:
DateTimeFormatter formatterWithSingleQuote = DateTimeFormatter.ofPattern("H mm'' ss\"");
System.out.println(LocalTime.now(ZoneId.of("Europe/London")).format(formatterWithSingleQuote));
10 28' 34"
DateTimeFormatter formatterWithSingleQuoteInsideSingleQuotes
= DateTimeFormatter.ofPattern("hh 'o''clock' a, zzzz", Locale.ENGLISH);
System.out.println(ZonedDateTime.now(ZoneId.of("America/Los_Angeles"))
.format(formatterWithSingleQuoteInsideSingleQuotes));
02 o'clock AM, Pacific Daylight Time
All of the formatters above can be used for parsing too. For example:
LocalTime time = LocalTime.parse("16 43' 56\"", formatterWithSingleQuote);
System.out.println(time);
16:43:56
The SimpleDateFormat class used when this question was asked nearly 10 years ago is notoriously troublesome and long outdated. I recommend that instead you use java.time, the modern Java date and time API. Which is why I demonstrate just that.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of DateTimeFormatter
You can use String.format as documented in java.util.Formatter:
Calendar c = ...;
String s = String.format("%tY year", c);
// -> s == "2010 year" or whatever the year actually is
java.text.SimpleDateFormat
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
String formattedDate = formatter.format(date);
You'll get more info here link text
Is there a simple way to parse a date that may be in MM/DD/yyyy, or M/D/yyyy, or some combination? i.e. the zero is optional before a single digit day or month.
To do it manually, one could use:
String[] dateFields = dateString.split("/");
int month = Integer.parseInt(dateFields[0]);
int day = Integer.parseInt(dateFields[1]);
int year = Integer.parseInt(dateFields[2]);
And validate with:
dateString.matches("\\d\\d?/\\d\\d?/\\d\\d\\d\\d")
Is there a call to SimpleDateFormat or JodaTime that would handle this?
Yep, use setLenient:
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
df.setLenient(true);
System.out.println(df.parse("05/05/1999"));
System.out.println(df.parse("5/5/1999"));
java.time
Java 8 and later includes the java.time framework. This framework obsoletes the old java.util.Date/.Calendar classes discussed in the other answers here.
The java.time.format package and its java.time.format.DateTimeFormatter class use pattern codes similar to that seen in the accepted Answer by Ray Myers. While similar, they vary a bit. In particular they are strict about the number of repeated characters. If you say MM, then the month must have padded zero or else you get a DateTimeParseException. If the month number may or may not have a padding zero, simply use the single-character M.
In this example code, note how the month number of the input string has a padding zero while the day-of-month number does not. Both are handled by the single-character pattern.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern ( "M/d/yyyy" );
LocalDate localDate = formatter.parse ( "01/2/2015" , LocalDate :: from );
Dump to console.
System.out.println ( "localDate: " + localDate );
localDate: 2015-01-02
Looks like my problem was using "MM/DD/yyyy" when I should have used "MM/dd/yyyy". Uppercase D is "Day in year", while lowercase d is "Day in month".
new SimpleDateFormat("MM/dd/yyyy").parse(dateString);
Does the job. Also, "M/d/y" works interchangeably. A closer reading of the SimpleDateFormat API Docs reveals the following:
"For parsing, the number of pattern letters is ignored unless it's needed to separate two adjacent fields."