I'm having trouble finding a post where the solution is something else besides
Get day difference and divide by 7
I'm looking to get the difference in calendar weeks between two dates, where the weeks start on Mondays.
For instance, the number of weeks between Nov 4th, 2019 and Nov 10th, 2019 should be 0.
However, the number of weeks between Nov 10th, 2019 and Nov 11th, 2019 should be 1.
The solution should also account for dates in different years. Any solutions that use LocalDate?
ChronoUnits have a between method which returns the number of complete units between a start and end date/time. To count weeks Monday to Sunday, you could "round down" your dates to the previous Monday. In your case it could look like this:
LocalDate start = LocalDate.of(2019, 11, 10);
LocalDate end = LocalDate.of(2019, 11, 11);
LocalDate mondayStart = start.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate mondayEnd = end.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
System.out.println(ChronoUnit.WEEKS.between(mondayStart, mondayEnd));
I would start with a known Monday and calculate the week number counting from then.
1st Jan 1962 was a Monday (other Mondays are available).
LocalDate knownMonday = LocalDate.of(1962,1,1);
LocalDate start = LocalDate.of(2019, 11, 10);
LocalDate end = LocalDate.of(2019, 11, 11);
long sWeek = (long)Math.floor(knownMonday.until(start,ChronoUnit.DAYS)/7.0);
long lWeek = (long)Math.floor(knownMonday.until(end ,ChronoUnit.DAYS)/7.0);
System.out.println(lWeek-sWeek);
Use GregorianCalendar . It has methods for computing differences in terms of weeks.
Related
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.
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.
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
To manage dates I'm using the class Calendar. I'm saving timestamps in a DB, using
Calendar timestamp = Calendar.getInstance();
I'm able to save and retrieve the dates from the DB using long values to store dates.
This is my problem: when I have a timestamp to store, I need to know if in the DB there is already a timestamp belonging to the same week. To do this I thought I could use the couple of methods:
timestamp.get(Calendar.YEAR) and timestamp.get(Calendar.WEEK_OF_YEAR)
to uniquely identify a week, but this isn't true for weeks having days belonging to two consecutive years.
This is an example of what I get for the last week of the year 2014.
Saturday, December 27, 2014
year: 2014
week: 52
Sunday, December 28, 2014
year: 2014
week: 1
Monday, December 29, 2014
year: 2014
week: 1
Tuesday, December 30, 2014
year: 2014
week: 1
Wednesday, December 31, 2014
year: 2014
week: 1
Thursday, January 1, 2015
year: 2015
week: 1
Friday, January 2, 2015
year: 2015
week: 1
Saturday, January 3, 2015
year: 2015
week: 1
Sunday, January 4, 2015
year: 2015
week: 2
The days from 28/12/2014 to 3/1/2015 belong to the same week (in java the week starts on Sunday), but they got a different year, so the couple (YEAR, WEEK_OF_YEAR) doesn't give me the info that they belong to the same week.
How can I solve?
As suggested by #basil, I tried the Joda-Time library and I found there the solution to my problem.
To get the week of the year I can use the method getWeekOfWeekyear() and to get the year I can use the method getWeekyear()
In this way, the couple of values uniquely identify the day belonging to the same week, with the week starting in Monday and ending in Sunday (another added value compared to the Java calendar class).
Following the same example of the question.
DateTime[] timestamp = new DateTime[10];
for(int i=0; i<10; i++){
//set the start of the period
DateTime time = new DateTime(2014, 12, 27, 18, 30, 50);
//add i days to the start date
timestamp[i] = time.plus(Period.days(i));
int weekYear = timestamp[i].getWeekyear();
int weekOfWeekyear = timestamp[i].getWeekOfWeekyear();
Log.d(Constants.LOG_TAG, timestamp[i].toString("EEEE, MMMM d, yyyy") + " | year.week = " + weekYear + "." + weekOfWeekyear);
}
The result is:
Saturday, December 27, 2014 | year.week = 2014.52
Sunday, December 28, 2014 | year.week = 2014.52
Monday, December 29, 2014 | year.week = 2015.1
Tuesday, December 30, 2014 | year.week = 2015.1
Wednesday, December 31, 2014 | year.week = 2015.1
Thursday, January 1, 2015 | year.week = 2015.1
Saturday, January 3, 2015 | year.week = 2015.1
Sunday, January 4, 2015 | year.week = 2015.1
Monday, January 5, 2015 | year.week = 2015.2
It looks like there are a lot of other advantages in using this library and it will be useful for other calculations I have to perform.
Thank you #basil.
To get the first and last day of the week relative to a date, do this:
Calendar cal = Calendar.getInstance();
cal.setTime(date); // date to check
cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
Date startOfWeek = cal.getTime();
cal.add(Calendar.DAY_OF_MONTH, 6);
Date endOfWeek = cal.getTime();
Disclaimer: I stole part of that from How to get the first day of the current week and month?
Then use a where clause that check the date range.
SELECT * FROM mytable WHERE mydate >= ? AND mydate <= ?
If your stored dates include time, you'd want the start of next week as the upper-exclusive boundary, by adding 7 instead of 6 days above.
SELECT * FROM mytable WHERE mydate >= ? AND mydate < ?
To show the date logic, here's some test code:
// Start on 12/27/2014
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2014, Calendar.DECEMBER, 27);
// Collect 10 dates to test (start date + next 9 days)
Date[] dates = new Date[10];
dates[0] = cal.getTime();
for (int i = 1; i < dates.length; i++) {
cal.add(Calendar.DAY_OF_MONTH, 1);
dates[i] = cal.getTime();
}
// Test loop
SimpleDateFormat fmt = new SimpleDateFormat("EEE d/M/yyyy");
for (Date date : dates) {
// Code to test
cal.setTime(date);
cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
Date startOfWeek = cal.getTime();
cal.add(Calendar.DAY_OF_MONTH, 6);
Date endOfWeek = cal.getTime();
// Show result
System.out.println(fmt.format(date) + " -> " + fmt.format(startOfWeek) +
" - " + fmt.format(endOfWeek));
}
Output
Sat 27/12/2014 -> Sun 21/12/2014 - Sat 27/12/2014
Sun 28/12/2014 -> Sun 28/12/2014 - Sat 3/1/2015
Mon 29/12/2014 -> Sun 28/12/2014 - Sat 3/1/2015
Tue 30/12/2014 -> Sun 28/12/2014 - Sat 3/1/2015
Wed 31/12/2014 -> Sun 28/12/2014 - Sat 3/1/2015
Thu 1/1/2015 -> Sun 28/12/2014 - Sat 3/1/2015
Fri 2/1/2015 -> Sun 28/12/2014 - Sat 3/1/2015
Sat 3/1/2015 -> Sun 28/12/2014 - Sat 3/1/2015
Sun 4/1/2015 -> Sun 4/1/2015 - Sat 10/1/2015
Mon 5/1/2015 -> Sun 4/1/2015 - Sat 10/1/2015
tl;dr
You must understand the difference between a “normal” calendar year and a standard week-based year. The first/last few days of a calendar year may appear in the previous/next week-based year.
The calendar year of 2014 ends on December 31, 2014 of course. But that last day of 2014 lands in the next week-based-year, 2015.
The last day of week-based year 2014 ends on December 28, 2014 (calendar year). The first day of 2015 week-based-year is December 29, 2014 (calendar year). The calendar graphic above should make these facts clear.
2014-12-28 = 2014-W52-7
2014-12-29 = 2015-W01-1
2015-01-01 = 2015-W01-4
Ask for week-based week and year number:
myZonedDateTime.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR )
…and…
myZonedDateTime.get( IsoFields.WEEK_BASED_YEAR )
Simply put: For the last/first few days of the year, calling myZonedDateTime.getYear() and myZonedDateTime.get( IsoFields.WEEK_BASED_YEAR ) may differ in results.
java.time
The modern approach uses the java.time classes that supplanted the troublesome old legacy date-time classes.
To manage dates I'm using the class Calendar. I'm saving timestamps in a DB, using Calendar timestamp = Calendar.getInstance();
Instead, use ZoneDateTime to capture the current moment with a wall-clock time used by people of a certain region (time zone).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
I'm able to save and retrieve the dates from the DB using long values to store dates.
No, use an approriate data type, both in defining your database column and your Java code.
To store a moment, a specific moment on the timeline, in a SQL standard compliant database, define your column as TIMESTAMP WITH TIME ZONE.
With JDBC 4.2 and later, you can directly exchange java.time objects with your database.
myPreparedStatement.setObject( … , zdt ) ;
Retrieve as an Instant, a moment in UTC, may be the best route in the other direction. Then adjust into your desired time zone.
Instant instant = myResultSet.getObject( … , Instant.class ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
This is my problem: when I have a timestamp to store, I need to know if in the DB there is already a timestamp belonging to the same week. To do this I thought I could use the couple of methods: timestamp.get(Calendar.YEAR) and timestamp.get(Calendar.WEEK_OF_YEAR)
Unfortunately, the meaning of a week in the confusing Calendar class varies by locale.
Perhaps your own definition agrees with the standard ISO 8601 definition of a week.
The first day is Monday, running through Sunday.
Week number one of a week-based year contains the first Thursday of the calendar year.
A week-based year has either 52 or 53 weeks.
The first/last few days of a calendar year may appear in the previous/next week-based year.
Note that determining a week requires determining a date. And determining a date requires a time zone. For any given moment, the date varies around the globe by zone. So be clear on your use of time zone (ZoneId) as seen above. In other words, a week number is contextual; it depends on a date in a particular zone.
Access a TemporalField in ZonedDateTime::get. The IsoFields class defines the constants you need.
int week = zdt.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR ) ;
int weekBasedYear = zdt.get( IsoFields.WEEK_BASED_YEAR ) ;
Even better, add the ThreeTen-Extra library to your project to use YearWeek class.
org.threeten.extra.YearWeek yw = YearWeek.from( zdt ) ;
to uniquely identify a week, but this isn't true for weeks having days belonging to two consecutive years.
This is an example of what I get for the last week of the year 2014.
Saturday, December 27, 2014 year: 2014 week: 52
In a standard week, 2014-12-27 is in week 52.
YearWeek.from( LocalDate.parse( "2014-12-27" ) ).toString()
2014-W52
The end of 2014 happens to land in the following week-based year (2015).
YearWeek.from( LocalDate.parse( "2014-12-31" ) ).toString()
2015-W01
You can compare YearWeek. Call equals, isBefore, or isAfter methods.
boolean sameYearWeek = YearWeek.from( someZdt ).equals( YearWeek.from( otherZdt ) )
Terminology
So, be sure that in your documentation, code comments, and discussions, you always distinguish between a calendar year and week-based year. Never say "the year" as that is terribly ambiguous.
Add in financial years for even more confusion. And some industries have their own definitions of years and seasons and so on. So be careful with your terms.
Beware of calendaring software settings
Never assume the definition of a week number. Be sure the source of such a number has the same definition of week as you, such as ISO 8601 definition.
For example, the Calendar app supplied by Apple with macOS defaults to a "Gregorian" calendar definition of week. As for what that means, I do not know as I could not find any documentation as to their intent/definition. For ISO 8601 weeks, you must change a setting away from default.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
I'm using the library DateTime to store date values for birthdays.
DateTime dateTime01Abegin = new DateTime(2013, 5, 23, 00, 00);
DateTime dateTime01Bbegin = new DateTime(2012, 5, 22, 00, 00);
Running the method .getDayOfYear() on them, I am getting a value of 143 for both. But one is May 23rd and one is May 22nd - I can't figure why they're returning the same value!
2012 has 366 (february 29) days and 2013 has 365, that's why both dates return 143.
The count of the days in a year has an offset of one day in leap years, since after february 28 leap years have an additional day compared to normal ones.
Not every year is 365 days long, some years are 366 days long.
2012 is a leap year, which means that it has an additional day, February 29th. For dates prior to February 28th, the .getDayOfYear() will return the same values for similar dates for any year. For dates after February 28th, .getDayOfYear() will return the same values for similar dates if both of those dates are in a leap year, or if both of those dates are not in a leap year. Otherwise, they should be off by one.
Leapyears. 2012 is a leapyear, so there was a Feb 29th, pushing all the "later" dates up one slot, so your May 22nd is actually day 143 in both years.
2012 was a leap year. So may 23 2013 came 1 day before may 23 2012.