I encountered a weird bug while running some code. Here is a simple version to showcase the same.
public class DateTest {
public static void main(String[] args) {
LocalDate decLast = LocalDate.of(2015, 12, 31);
LocalDate novLast = LocalDate.of(2015, 11, 30);
LocalDate octLast = LocalDate.of(2015, 10, 31);
System.out.println(decLast+" "+novLast+" "+octLast);
System.out.println(decLast.format(DateTimeFormatter.ofPattern("dd M YY"))+" "
+novLast.format(DateTimeFormatter.ofPattern("dd M YY"))+" "
+octLast.format(DateTimeFormatter.ofPattern("dd M YY")));
}
}
This returned the following results
2015-12-31 2015-11-30 2015-10-31
31/12/16 30/11/15 31/10/15
Somehow, the 31st Dec 2015 was converted to 31st Dec 2016.
I wrote a for loop to do the same for different years and found that there is variation in many years. The error doesn't exist for any dates below 26th December. Is this a bug or am I missing something here?
The upper case Y is the "week based year", you are looking for the lower case y instead.
From the linked Wikipedia article (emphasis by me):
An ISO week-numbering year (also called ISO year informally) has 52 or 53 full weeks. That is 364 or 371 days instead of the usual 365 or 366 days. The extra week is referred to here as a leap week, although ISO 8601 does not use this term. Weeks start with Monday. The first week of a year is the week that contains the first Thursday of the year (and, hence, always contains 4 January). ISO week year numbering therefore slightly deviates from the Gregorian for some days close to 1 January.
[...]
For example, 29 December 2014 is ISO 2015-W1-1, i.e., it is in year 2015 instead of 2014.
Cf. also: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
Related
This question already has answers here:
Why does Java's java.time.format.DateTimeFormatter#format(LocalDateTime) add a year?
(2 answers)
Using DateTimeFormatter on january first cause an invalid year value
(1 answer)
Closed 1 year ago.
The following code (in main)
LocalDate d ;
DateTimeFormatter formatter;
d = LocalDate.of(2021, 11, 14);
System.out.println(d);
formatter = DateTimeFormatter.ofPattern("MM.dd.YYYY");
System.out.println(formatter.format(d));
formatter = DateTimeFormatter.ofPattern("YYYY/MM/dd");
System.out.println(formatter.format(d)+ "\n");
d = LocalDate.of(2021, 1, 1);
System.out.println(d);
formatter = DateTimeFormatter.ofPattern("MM.dd.YYYY");
System.out.println(formatter.format(d));
formatter = DateTimeFormatter.ofPattern("YYYY/MM/dd");
System.out.println(formatter.format(d));
outputs in my Netbeans 12.5 with the latest or fairly recent Oracle Java 17
run:
2021-11-14
14.11.2021
2021/11/14
2021-01-01
01.01.2020
2020/01/01
BUILD SUCCESSFUL (total time: 0 seconds)
The formatted output for today, 14-NOV-2021, is ok, but output for 01-JAN-2021 is wrong. Formatted output prints 2020 , one year off.
Can you repeat this? If so: Any ideas why this happens?
According to the Javadoc of java.time.format.DateTimeFormatter
y (lowercase) represents the year-of-era and
Y (uppercase) represents the week-based-year.
The week-based date/time system is defined in ISO 8601 (see: Wikipedia article).
You want u, not Y:
Y is week-based year. If you're an accountant, this is important. If you're not, this is the devil: It seems right almost always, except in certain highly exotic dates, such as jan 1st. Weeks customarily are defined as: "Whatever year it is on the thursday of that week, that's the year that week belongs to", so if e.g. Jan 1st falls on a friday, then the week that contains Jan 1st is of the previous year (as the thursday of that week was dec 31st).
y is year as you mostly know it, except it doesn't really do what you want when go to BC years. It's not likely to come up, but then neither is the week-based-year thing. Nobody likes Year-2k style bugs, right?
u truly does what you imagined. It just gives you the year. Negative if need be.
I need to get the date by passing these parameters
year
week number (in a month) i.e. 1,2,3,4,5
day number (in a week) 0 (Sunday) to 6 (Saturday)
Month
I looked for a constructor in Calendar class but does not contain these parameters.
In spite of your tags I agree with Joe C’s comment, you should prefer the modern Java date and time API (AKA known as java.time or JSR-310) over the long outdated Calendar and friends. The modern classes are more programmer friendly and nicer to work with, they come with fewer surprises, lead to clearer and more natural code (at least when you get used to the fluent style) and are easier to debug should you make a bug.
I don’t know how you count your weeks of the month. I have assumed that the first week starts on the first of the month and lasts 7 days (so the second week starts on the 8th and the 5th week on the 29th day of the month). If you count differently, please see Hugo’s answer.
I suggest this method:
public static LocalDate weekNoAndDayToDate(Year year, Month month, int weekOfMonth, int dayOfWeek) {
// find day of week: convert from 0 (Sunday) to 6 (Saturday)
// to DayOfWeek, from 1 (Monday) to 7 (Sunday)
DayOfWeek dow;
if (dayOfWeek == 0) { // Sunday
dow = DayOfWeek.SUNDAY;
} else {
dow = DayOfWeek.of(dayOfWeek);
}
return year.atMonth(month)
.atDay(1)
.with(TemporalAdjusters.nextOrSame(dow))
.with(ChronoField.ALIGNED_WEEK_OF_MONTH, weekOfMonth);
}
With this we may do for example
LocalDate today = weekNoAndDayToDate(Year.of(2017), Month.JULY, 1, 1);
This yields
2017-07-03
If you need either a Date or a GregorianCalendar, for example for an old API that you cannot change, do one of the following:
Date oldfashiondDateObject
= Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
GregorianCalendar oldfashionedCalendarObject
= GregorianCalendar.from(today.atStartOfDay(ZoneId.systemDefault()));
In both cases the result will be different in different time zones (one of the inherent troubles of the old classes). On my computer the first yields a Date of
Mon Jul 03 00:00:00 CEST 2017
The second yields a GregorianCalendar equal to the same point in time.
To create date from year, week number and week's day use java.util.Calendar instance:
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2017);
cal.set(Calendar.WEEK_OF_YEAR, 26);
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
To convert from Calendar to java.util.Date :
Date date = cal.getTime();
To convert Date into java.time.LocalDateTime use :
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
As #Ole V.V. explained, you need to define in what day the week starts, and how many days it must have to be considered the first week.
For example, the ISO-8601 standard considers:
Monday to be the first day-of-week.
The minimal number of days in the first week: the standard counts the first week as needing at least 4 days
The month is divided into periods where each period starts on the defined first day-of-week. The earliest period in the same month is referred to as week 0 if it has less than the minimal number of days and week 1 if it has at least the minimal number of days.
Depending on how you define those, you can have different results. Consider the calendar for July 2017:
July 2017
Su Mo Tu We Th Fr Sa
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
If we consider ISO's definition, we have:
week zero - 2017-07-01 to 2017-07-02
week 1: from 2017-07-03 to 2017-07-09
week 2: from 2017-07-10 to 2017-07-16
week 3: from 2017-07-17 to 2017-07-22
week 4: from 2017-07-23 to 2017-07-30
week 5: 2017-07-31
If we consider first day of week as Sunday and minimal number of days in the first week as 1, we have:
week 1: 2017-07-01
week 2: from 2017-07-02 to 2017-07-08
week 3: from 2017-07-09 to 2017-07-15
week 4: from 2017-07-16 to 2017-07-21
week 5: from 2017-07-22 to 2017-07-29
week 6: from 2017-07-30 to 2017-07-31
As a solution with Calendar was already posted, I'm gonna write one using the new API. 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 <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's 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.
I'm using a DateTimeFormatter because it takes all the fields (month, year, day of week and week of month) and resolves the day accordingly, creating a LocalDate (which has the day/month/year fields). I'm also using the WeekFields class, which can be configured to use different week definitions (first day and minimal number of days in first week)
There's also a little adjustment to consider Sunday as zero:
public LocalDate getDate(int weekOfMonth, int dayOfWeek, int month, int year) {
// you can customize your week definition (first day of week and mininum days in first week)
WeekFields wf = WeekFields.of(DayOfWeek.SUNDAY, 2);
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// week of month, using week definition from WeekFields
.appendValue(wf.weekOfMonth()).appendPattern(" ")
// day of week
.appendValue(ChronoField.DAY_OF_WEEK)
// month and year
.appendPattern(" M/yyyy")
// create formatter
.toFormatter();
return LocalDate.parse(weekOfMonth + " " +
// Sunday is 0, adjusting value
(dayOfWeek == 0 ? 7 : dayOfWeek) + " " + month + "/" + year, fmt);
}
Using this code (week starts on Sunday, and 2 days are required to be considered the first week - otherwise week will be zero as in the first example above):
LocalDate d = getDate(1, 6, 7, 2017);
d will be 2017-07-08 (Saturday in the week 1 of July 2017).
If you want to use ISO 8601 definition, use the constant WeekFields.ISO instead of using WeekFields.of() method.
As #Ole V.V. suggested in the comments, it can also be done without creating a formatter: get the first dayOfWeek of the month and adjust it to the desired weekOfMonth:
public LocalDate getDate(int weekOfMonth, int dayOfWeek, int month, int year) {
// you can customize your week definition (first day of week and mininum days in first week)
WeekFields wf = WeekFields.of(DayOfWeek.SUNDAY, 2);
// Sunday is 0, adjusting value
DayOfWeek dow = DayOfWeek.of(dayOfWeek == 0 ? 7 : dayOfWeek);
// get the first weekday of the month
LocalDate first = LocalDate.of(year, month, 1).with(TemporalAdjusters.nextOrSame(dow));
// check in which week this date is
int week = first.get(wf.weekOfMonth());
// adjust to weekOfMonth
return first.plusWeeks(weekOfMonth - week);
}
This works exactly the same way, but without the need of a formatter - I've tested with dates from year 1600 to 2100 and it gets the same results.
PS: Calendar also has a similar configuration via the methods setFirstDayOfWeek() and setMinimalDaysInFirstWeek(). You can check the default configuration calling the respective get methods.
There is a constant in the Calendar class called: UNDECIMBER. It describes the 13th month.
Is there a useful purpose for this constant? In Wikipedia it is written that it is for the lunar calendar. But there is no implementation for such calendar.
And does there exist any solutions for the 14th month (Duodecimber)?
I didn't found so much in the web, and I would like to find out more about this topic.
As already said, some lunar (and other ancient) calendars have 13 months. One example is the Coptic Calendar.
Although there are no implementations of calendars with 13 months that extends java.util.Calendar, in Java 8's new API there are some. With the introduction of the new java.time API, it was also created the ThreeTen Extra project, which contains an implementation for that.
The class is org.threeten.extra.chrono.CopticChronology, which extends the native java.time.chrono.Chronology. I've just made a sample code to create a date in this calendar and loop through its months:
// Coptic calendar
CopticChronology cal = CopticChronology.INSTANCE;
// range for month of year (from 1 to 13)
System.out.println("month range: " + cal.range(ChronoField.MONTH_OF_YEAR)); // 1 - 13
// getting a date in Coptic calendar and loop through the months
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
// September 11th is equivalent to 01/01 in Coptic calendar
CopticDate d = cal.date(LocalDate.of(2017, 9, 11));
for (int i = 0; i < 14; i++) {
System.out.println(fmt.format(d));
d = d.plus(1, ChronoUnit.MONTHS);
}
The output is:
month range: 1 - 13
01/01/1734
01/02/1734
01/03/1734
01/04/1734
01/05/1734
01/06/1734
01/07/1734
01/08/1734
01/09/1734
01/10/1734
01/11/1734
01/12/1734
01/13/1734
01/01/1735
Note that the year changed just after the 13th month.
The ThreeTen Extra project also has an implementation for the Ethiopian calendar, which has 13 months as well.
And, as an example of a calendar with 14 months, there's the PaxChronology class, which implements the Pax Calendar: a proposed reform calendar system, but not currently in use, as far as I know.
Quoting wikipedia:
The common year is divided into 13 months of 28 days each, whose names are the same as in the Gregorian calendar, except that a month called Columbus occurs between November and December. The first day of every week, month and year would be Sunday.
In leap years, a one-week month called Pax would be inserted after Columbus.
And according to javadoc:
Leap years occur in every year whose last two digits are divisible by 6, are 99, or are 00 and the year is not divisible by 400.
Example:
PaxChronology paxCal = PaxChronology.INSTANCE;
System.out.println("month range: " + paxCal.range(ChronoField.MONTH_OF_YEAR));
PaxDate pd = paxCal.date(1930, 1, 1);
for (int i = 0; i < 15; i++) {
// fmt is the same DateTimeFormatter from previous example
System.out.println(fmt.format(pd));
// adjusting for first day of next month - using TemporalAdjuster because
// adding 1 ChronoUnit.MONTHS throws an exception for 14th month (not sure why)
pd = pd.plus(30, ChronoUnit.DAYS).with(TemporalAdjusters.firstDayOfMonth());
}
Output:
month range: 1 - 13/14
01/01/1930
01/02/1930
01/03/1930
01/04/1930
01/05/1930
01/06/1930
01/07/1930
01/08/1930
01/09/1930
01/10/1930
01/11/1930
01/12/1930
01/13/1930
01/14/1930
01/01/1931
You can notice that the year changes after the 14th month.
The range is 1 - 13/14 because years can have 13 or 14 months, depending if it's a leap year or not.
The Calendar.UNDECIMBER is an additional constant in the Calendar class that is not typically used in the widely used Gregorian Calendar but certain Lunar calendars use a 13th month. That's the purpose of this field.
Refer to the Java Docs below:
https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html#UNDECIMBER
Wikipedia article for this:
https://en.wikipedia.org/wiki/Undecimber
There is also a mention of a 14th month - Duodecimber in the wiki. Unfortunately, Java does not (yet) support that.
Hope this helps!
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyww");
dateTimeFormatter.print(new DateTime(2016, 1, 1, 1, 1).withZone(DateTimeZone.UTC))
returns 201653
Why is it 53 week of 2016 rather than 2015?
What you are looking for is the week-based-year (symbol x), not the year of era (symbol y). See also the pattern syntax used by Joda-Time which deviates from that of SimpleDateFormat or Java-8. So the solution should look like:
DateTimeFormatter f = DateTimeFormat.forPattern("xxxxww");
String s = f.print(new DateTime(2016, 1, 1, 1, 1).withZone(DateTimeZone.UTC));
System.out.println(s); // 201553
Because January 1st was Friday. And in that case, that week counts as last week of 2015, and the first week of 2016 will start on first Monday (January 4th).
That is implemented in accordance with ISO 8601 standard:
There are several mutually equivalent and compatible descriptions of week 01:
- the week with the year's first Thursday in it (the formal ISO definition),
- the week with 4 January in it,
- the first week with the majority (four or more) of its days in the starting year, and
- the week starting with the Monday in the period 29 December – 4 January.
Either I don't understand the method getActualMaximum(int) or the field WEEK_OF_YEAR, or there's a Sun bug involved (or all three)...could someone explain to me why (at least in a German locale...) the following code:
Locale.setDefault( Locale.GERMAN );
Calendar c = Calendar.getInstance();
c.set( Calendar.YEAR, 2010 );
c.set( Calendar.MONTH, 0 );
c.set( Calendar.DAY_OF_MONTH, 1 );
System.out.println("max: "+c.getActualMaximum( Calendar.WEEK_OF_YEAR ));
System.out.println("actual: "+c.get( Calendar.WEEK_OF_YEAR ));
produces the following output:
max: 52
actual: 53
Here's the Javadoc of getActualMaximum(int):
Returns the maximum value that the
specified calendar field could have,
given the time value of this Calendar.
For example, the actual maximum value
of the MONTH field is 12 in some
years, and 13 in other years in the
Hebrew calendar system.
Edit
The plot thickens. In an English locale (-Duser.language=en -Duser.country=us) the output is:
max: 52
actual: 1
Seems to point to it being a Sun bug for German locales?
This information is correct:
max: 52
actual: 53
The year 2010 has a maximum of 52 weeks. The actual week is 53, since 2009 has maximum 53 weeks and most weeks start on sunday or monday. Week 1 is in most cases the first week of the year with 4 days in january. Since the week of the january 1st has only 2 or 3 days in 2010, the week is considered part of 2009.
Most likely the english locale has different rules for determing week 1, like the first week is the week of january 1st.
Wikipedia explains it correctly: wikipedia week article
The problem is, that January 1st 2010 is in week 53 of 2009 (in Germany), but year 2010 only has 52 weeks (December 31st 2010 is in week 52). The Java Calendar object unfortunately does not have a field for the year, to which the week number relates.