How do you determine which day of the week is considered the “start” according to a given Locale using Joda-Time?
Point: Most countries use the international standard Monday as first day of week (!). A bunch others use Sunday (notably USA). Others apparently Saturday. Some apparently Wednesday?!
Wikipedia "Seven-day week"#Week_number
Joda-Time uses the ISO standard Monday to Sunday week.
It does not have the ability to obtain the first day of week, nor to return the day of week index based on any day other than the standard Monday. Finally, weeks are always calculated wrt ISO rules.
There's no reason you can't make use of the JDK at least to find the "customary start of the week" for the given Locale. The only tricky part is translating constants for weekdays, where both are 1 through 7, but java.util.Calendar is shifted by one, with Calendar.MONDAY = 2 vs. DateTimeConstants.MONDAY = 1.
Anyway, use this function:
/**
* Gets the first day of the week, in the default locale.
*
* #return a value in the range of {#link DateTimeConstants#MONDAY} to
* {#link DateTimeConstants#SUNDAY}.
*/
private static final int getFirstDayOfWeek() {
return ((Calendar.getInstance().getFirstDayOfWeek() + 5) % 7) + 1;
}
Add a Locale to Calendar.getInstance() to get a result for some Locale other than the default.
Here is how one might work around Joda time to get the U.S. first day of the week:
DateTime getFirstDayOfWeek(DateTime other) {
if(other.dayOfWeek.get == 7)
return other;
else
return other.minusWeeks(1).withDayOfWeek(7);
}
Or in Scala
def getFirstDayOfWeek(other: DateTime) = other.dayOfWeek.get match {
case 7 => other
case _ => other.minusWeeks(1).withDayOfWeek(7)
}
Seems like you're out of luck, it looks like all of the provided Chronologies inherit the implementation from baseChronology, which supports only ISO definitions,
i.e. Monday=1 ... Sunday=7.
You would have to define your own LocaleChronology, possibly modeled on StrictChronology or LenientChronology, add a factory method:
public static LocaleChronology getInstance(Chronology base, Locale locale)
and override the implementation of
public final DateTimeField dayOfWeek()
with a re-implementation of java.util.Calendar.setWeekCountData(Locale desiredLocale) which relies on sun.util.resources.LocaleData..getCalendarData(desiredLocale).
This is what I came up with. The startOfWeek will always be the start of a Sunday and the endOfweek will always be an end of a Saturday(Start a of Monday).
DateTime startOfWeek;
DateTime endOfWeek;
// make sure Sunday is the first day of the week, not Monday
if (dateTime.getDayOfWeek() == 7) {
startOfWeek = dateTime.plusDays(1).weekOfWeekyear().roundFloorCopy().minusDays(1);
endOfWeek = dateTime.plusDays(1).weekOfWeekyear().roundCeilingCopy().minusDays(1);
} else {
startOfWeek = dateTime.weekOfWeekyear().roundFloorCopy().minusDays(1);
endOfWeek = dateTime.weekOfWeekyear().roundCeilingCopy().minusDays(1);
}
Here is the Scala code to get start and end day of week dynamically.
Note:- Make sure start and end are in order for example when start day is Sunday then Monday is end day of week
def shiftWeeksBy(startDayOfWeek: Int, currentWeekDay: Int) : Int = (startDayOfWeek, currentWeekDay) match {
case (s, c) if s <= c | s == 1 => 0 //start day of week is Monday -OR- start day of week <= current week day
case _ => 1
}
def getStartAndEndDays(initialTime: DateTime, startDayOfWeek: Int, endDayOfWeek: Int): (Option[DateTime], Option[DateTime]) = {
val currentDateWeekDay = initialTime.dayOfWeek.get
(Some(initialTime.minusWeeks(shiftWeeksBy(startDayOfWeek, currentDateWeekDay)).withDayOfWeek(startDayOfWeek).withTimeAtStartOfDay()),
Some(initialTime.plusWeeks(shiftWeeksBy(currentDateWeekDay, endDayOfWeek)).withDayOfWeek(endDayOfWeek)))
}
Output:- For 5th Jan 2021 start day of week is Thursday and end day of week is Wednesday then week begins with 2020-12-31 and end with 2021-01-06.
scala> getStartAndEndDays(new DateTime("2021-01-05T00:00:00.000"), 4, 3)
res5: (Option[org.joda.time.DateTime], Option[org.joda.time.DateTime]) = (Some(2020-12-31T00:00:00.000+05:30),Some(2021-01-06T00:00:00.000+05:30))
So your question is, how to get the DayOfWeek from a Joda DateTime object? What about this:
DateTime dt = new DateTime().withYear(2009).plusDays(111);
dt.toGregorianCalendar().getFirstDayOfWeek();
I used the following stub in Scala to obtain first and last days of the week from Joda DateTime
val today: DateTime = new DateTime()
val dayOfWeek: DateTime.Property = today.dayOfWeek()
val firstDayOfWeek: DateTime = dayOfWeek.withMinimumValue().minusDays(1)
val lastDayOfWeek: DateTime = dayOfWeek.withMaximumValue().minusDays(1)
Note: The minusDays(1) is only meant to make the week span from Sunday to Saturday (instead of the default Monday to Sunday known to Joda). For US (and other similar) locales, you can ignore this part
Related
I would like to reach date that is -1month +1day, which should be 0month difference from start date.
Using joda-time 2.10:
int day = 29;
LocalDate date1 = new LocalDate(new GregorianCalendar(2019, Calendar.JUNE, day).getTime());
LocalDate date2 = date1.plusMonths(-1).plusDays(1);
Months.monthsBetween(date1,date2).getMonths(); // returns 0 <- it's OK
but the same code with input int day = 30; returns -1 which is bad.
That looks like an inconsequence in Joda library.
That's a case: shift by -1month change date by shift month number and keep day number no greater than in input, but month-difference between dates are depend on day of month.
Do you know any alternative and working solution?
I have found JSR-310 with ChronoUnit - that solves the problem, BUT it needs Java8. I would like to stay on Java7.
I want to get Monday date from given week and year using Java 8 package java.time.
But at some point I am facing issue as it's not returning proper date.
private LocalDate getDateFromWeekAndYear(final String week,final String year){
LocalDate date = LocalDate.now();
date = date.with(WeekFields.ISO.dayOfWeek(), 1);
date = date.with(WeekFields.ISO.weekOfWeekBasedYear(), Long.parseLong(week));
date = date.with(WeekFields.ISO.weekBasedYear(), Long.parseLong(year));
return date;
}
For example:
If I pass week=1 and year=2013 then date is : 2012-12-31.
But if I pass week=53 and year=2015 then date is : 2014-12-29. I expected 2014-12-28.
Is there any logical mistake I am making or some other issue ?
This is astonishingly more difficult than the partially invalid expectations of OP and most answers show.
First to say: It is very important to define the correct order of week-based-manipulations. The OP has first applied day-manipulation, then year-based manipulation. The correct approach is in reverse! I will show the right helper method implementation:
public static void main(String... args) {
System.out.println(
getDateFromWeekAndYear("53", "2015")); // 2015-12-28, NOT 2014-12-28
System.out.println(
getDateFromWeekAndYear("53", "2015").get(WeekFields.ISO.weekOfWeekBasedYear())); // 53
System.out.println(
getDateFromWeekAndYear("53", "2014")); // 2014-12-29
System.out.println(
getDateFromWeekAndYear("53", "2014").get(WeekFields.ISO.weekOfWeekBasedYear())); // 1
}
private static LocalDate getDateFromWeekAndYear(final String week,final String year) {
int y = Integer.parseInt(year);
LocalDate date = LocalDate.of(y, 7, 1); // safer than choosing current date
// date = date.with(WeekFields.ISO.weekBasedYear(), y); // no longer necessary
date = date.with(WeekFields.ISO.weekOfWeekBasedYear(), Long.parseLong(week));
date = date.with(WeekFields.ISO.dayOfWeek(), 1);
return date;
}
If you don't respect this specific order then you will indeed get sometimes a 2014-date for the input 2015-W53 (depending on the current date).
Second problem: I have also avoided to start with current date in order to be not near start or end of calendar year (calendar year != week-based-year) and instead chosen midth of year as starting point.
The third problem is lenient handling of week 53 in (week-based)-year 2014. It does not exist because 2014 had only 52 weeks!!! A strict algorithm should recognize and reject such an input. Therefore I advise against using YearWeek.of(2014, 53) (in the external library Threeten-Extra) resulting in the first week of 2015, see also its javadoc. Better than such lenient handling would have been
YearWeek yw = YearWeek.of(2014, 52);
if (yw.is53WeekYear()) {
yw = YearWeek.of(2014, 53);
}
or using this code from my own time library Time4J (whose class CalendarWeek has extra i18n-features and extra week arithmetic in comparison with YearWeek):
CalendarWeek.of(2014, 53); // throws an exception
System.out.println(CalendarWeek.of(2014, 1).withLastWeekOfYear()); // 2014-W52
Only using java.time-package:
Using such external libraries would at least have helped to solve the first problem in a transparent way. If you are not willing to add an extra dependency then you can do this to handle week 53 if invalid:
If the expression WeekFields.ISO.weekOfWeekBasedYear() applied on the result of your helper method yields the value 1 then you know that week 53 was invalid. And then you can decide if you want to accept lenient handling or to throw an exception. But silent adjusting such an invalid input is IMHO bad design.
First, you need to calculate the first Monday of the first week of the year,
then simply plus multi of 7 to the date.
public static LocalDate firstMonday(int week, int year) {
LocalDate firstMonOfFirstWeek = LocalDate.now()
.with(IsoFields.WEEK_BASED_YEAR, year) // year
.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 1) // First week of the year
.with(ChronoField.DAY_OF_WEEK, 1); // Monday
// Plus multi of 7
return firstMonOfFirstWeek.plusDays( (week - 1) * 7);
}
public static void main(String[] args) {
System.out.println(firstMonday(1, 2013)); // 2012-12-31
System.out.println(firstMonday(53 ,2015 )); // 2015-12-28
}
tl;dr
YearWeek.of ( 2013 , 1 ).atDay ( DayOfWeek.MONDAY )
Incorrect expectations
Your expectations are not correct. The Monday of 2015-W53 is 2015-12-28, not 2014-12-28, year 2015 rather than 2014. There is no reason to expect 2014. Please edit your Question to explain your thoughts, if you need more explanation.
You may be confused about a calendar-year versus a week-based year. In the ISO 8601 definition of a week based year, week number one contains the first Thursday of the calendar-year. This means we have some overlap between the years. The last few days of a calendar-year may reside in the following week-based-year. And vice-versa, the first few days of a calendar-year may reside in the previous week-based-year.
As an example, you can see in the screenshot below, the last day of calendar 2012 (December 31) falls in week one of the following week-based-year of 2013 at week number 1. And in the other screen shot, we have the opposite, where the first three days of calendar 2016 (January 1, 2, & 3) land in the week-based-year of 2015 at week number 53.
The Monday of 2013-W01 is 2012-12-31.
The Monday of 2015-W53 is 2015-12-28.
YearWeek
I suggest adding the ThreeTen-Extra library to your project to make use of of the YearWeek class. Rather than pass around mere integer numbers for year and week, pass around objects of this class. Doing so makes your code more self-documenting, provides type-safety, and ensures valid values.
// Pass ( week-based-year-number, week-number ). *Not* calendar year! See the ISO 8601 standard.
YearWeek yw = YearWeek.of( 2013 , 1 );
You can pull any day from that week.
LocalDate ld = yw.atDay( DayOfWeek.MONDAY );
Let's try this kind of code.
YearWeek yw1 = YearWeek.of ( 2013 , 1 );
LocalDate ld1 = yw1.atDay ( DayOfWeek.MONDAY );
YearWeek yw2 = YearWeek.of ( 2015 , 53 );
LocalDate ld2 = yw2.atDay ( DayOfWeek.MONDAY );
System.out.println ( "yw1: " + yw1 + " Monday: " + ld1 );
System.out.println ( "yw2: " + yw2 + " Monday: " + ld2 );
yw1: 2013-W01 Monday: 2012-12-31
yw2: 2015-W53 Monday: 2015-12-28
Tip: To see those ISO 8601 standard week numbers on a Mac in Calendar.app, set System Preferences > Language & Region > Calendar > ISO 8601. Then in Calendar.app, set Preferences > Advanced > Show week numbers.
I wanted Monday of a week about 6 month ago - to be specific 26 weeks ago.. below code gave me the required date:
LocalDate.now().minusWeeks(26).with(WeekFields.ISO.dayOfWeek(), DayOfWeek.MONDAY.getValue())
For a span of time running from one date to another date, how to get the number of calendar months containing one or more days of my span?
So for example:
2016-01-23/2016-01-23 = 1 calendar month (January)
2016-01-31/2016-02-01 = 2 calendar months (January, February)
2016-01-23/2016-02-28 = 2 calendar months (January, February)
2016-01-15/2016-03-15 = 3 calendar months (January, February, March)
2016-01-15/2017-03-15 = 15 calendar months (Jan-Dec of 2016 plus January, February, March of 2017)
I do not define a month as “30 days”. I am asking about calendar months, January-December.
Similar to this Question but that asks about PHP/MySQL.
Calculate the "epoch" month of both dates, then subtract them and add 1.
Using LocalDate like in the other answer, an epochMonth() helper method makes it easy:
private static int monthsTouched(LocalDate fromDate, LocalDate toDate) {
return epochMonth(toDate) - epochMonth(fromDate) + 1;
}
private static int epochMonth(LocalDate date) {
return date.getYear() * 12 + date.getMonthValue();
}
Like the results in the question, both dates are inclusive.
Note: Validation skipped for brevity, e.g. what is result if fromDate > toDate?
Test
public static void main(String[] args) {
test("2016-01-23", "2016-01-23");
test("2016-01-31", "2016-02-01");
test("2016-01-23", "2016-02-28");
test("2016-01-15", "2016-03-15");
test("2016-01-15", "2017-03-15");
}
private static void test(String fromDate, String toDate) {
System.out.println(monthsTouched(LocalDate.parse(fromDate), LocalDate.parse(toDate)));
}
Output (matches results from question)
1
2
2
3
15
Use ChronoField.PROLEPTIC_MONTH, which returns a count of months from year zero:
import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
long monthsTouched = date2.getLong(PROLEPTIC_MONTH) - date1.getLong(PROLEPTIC_MONTH) + 1;
Adjust the start and end dates
The key is to adjust your dates.
Move the starting date to the first of the month
Move the ending date to the first of the following month
We move the ending to the next month after because the Half-Open approach is commonly used when considering spans of time. Half-Open means the beginning is inclusive while the ending is exclusive. So lunch hour runs from 12:00 to 13:00 but does not include the 61st minute of 1 PM. A week runs from Monday to Monday, for seven days not including that second Monday.
So a span running from the first of January to the first of March is two months rather than three because we run up to, but do not include, that last date, the first of March.
java.time
The java.time classes built into Java 8 and later make easier work of this.
For date-only values, without a time-of-day and without a time zone, use the LocalDate class.
LocalDate start = LocalDate.parse( "2016-01-31" );
LocalDate stop = LocalDate.parse( "2016-02-01" );
To adjust, use a TemporalAdjuster. Implementations can be found in the TemporalAdjusters class (note the plural 's'). We need firstDayOfMonth and firstDayOfNextMonth.
LocalDate startAdjusted = start.with( TemporalAdjusters.firstDayOfMonth() );
LocalDate stopAdjusted = stop.with( TemporalAdjusters.firstDayOfNextMonth() );
Now use the ChronoUnit class to calculate elapsed whole months.
long calendarMonthsTouched = ChronoUnit.MONTHS.between( startAdjusted , stopAdjusted );
span: 2016-01-31/2016-02-01
calendarMonthsTouched: 2
See this code live in IdeOne.com.
I want to create a calendar with Java 8. So far I have this:
YearMonth yearMonthObject = YearMonth.of(year, month);
int daysOfCurrentMonth = yearMonthObject.lengthOfMonth();
int i = 1;
ArrayList<Integer> Dayes = new ArrayList<Integer>();
for(i=1; i<=daysOfCurrentMonth; i++){
Dayes.add(i);
}
Dayes.forEach(value -> System.out.print(value));
which prints the days of the current month (for example May).
How can I determine that 1 is Sunday, 2 is Monday, 3 is Tuesday, ..., 8 is Sunday (next week), etc.?
You have a YearMonth object. For each day of the month, you can call atDay(dayOfMonth) to return a LocalDate at that specific day of month. With that LocalDate, you can then call:
getDayOfMonth() to get back the day of the month as an int;
getDayOfWeek() to get the day of the week as a DayOfWeek. This is an enumeration of all the days of the week.
As such, you should change your Dayes list to hold LocalDates instead of Integers, and then you can have, for example:
YearMonth yearMonthObject = YearMonth.of(year, month);
int daysOfCurrentMonth = yearMonthObject.lengthOfMonth();
ArrayList<LocalDate> dayes = new ArrayList<LocalDate>();
for(int i = 1; i <= daysOfCurrentMonth; i++){
dayes.add(yearMonthObject.atDay(i));
}
dayes.forEach(value -> System.out.println(value.getDayOfMonth() + " " + value.getDayOfWeek()));
This will print each day of that month followed by the corresponding day of the week.
As a side-note, you can get a real display value for the day of week (instead of the name() of the enum like above) by calling getDisplayName(style, locale). The style represents how to write the days (long form, short form...) and the locale is the locale to use for the display name. An example would be:
value.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH)
which would output the full text of the day of the week in English. Sample output for 04/2016 with the above change:
1 Friday
2 Saturday
3 Sunday
4 Monday
5 Tuesday
This may be a bit of a 'hack' solution, but if you are trying to make a calendar for any year, you may have to use an 'anchor date' (Such as January 1, 1800 as a Wednesday). You then could calculate the number of days that happened between January 1st, 1800 and your current year/month/day. Once you figured out how many days have passed, using Modular 7 you could determine what day it is, and then populate the calendar for the month from there.
I have a javafx.scene.control.DatePicker. I want to extract the (Locale) week number from the selected date. Until now i haven't found a solution and i prefer not to write my own algorithm. I use Java8 and hope it is possible in the new java time library.
The Java-8-solution can take into account the local definition of a week using the value of a date-picker:
LocalDate date = datePicker.getValue(); // input from your date picker
Locale locale = Locale.US;
int weekOfYear = date.get(WeekFields.of(locale).weekOfWeekBasedYear());
Also keep in mind that the popular alternative Joda-Time does not support such a localized week-of-year fields. For example: In ISO-8601 (widely used) the week starts with Monday, in US with Sunday. Also the item when the first week of year starts in a given calendar year is dependent on the locale.
You can use http://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#get-java.time.temporal.TemporalField-
LocalDate localDate = LocalDate.of(2014, 9, 18); // assuming we picked 18 September 2014
int weekNumber = localDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
This will give you the week number based on ISO convention.
For a locale based evaluation :
LocalDate localDate = LocalDate.of(2014, 9, 18); // assuming we picked 18 September 2014
WeekFields weekFields = WeekFields.of(Locale.US);
int weekNumber = localDate.get(weekFields.weekOfWeekBasedYear());
FX DatePicker is based on the new (to jdk8) Date/Time api - time to learn how-to use it (not entirely sure I found the shortest way, though - corrections welcome :-)
The picker's value is a LocalDate, which can be queried for certain TemporalFields. Locale-aware week-related fields are provided by the WeekFields class, f.i. weekOfYear:
DatePicker picker = new DatePicker();
picker.valueProperty().addListener((p, oldValue, newValue) -> {
if (newValue == null) return;
WeekFields fields = WeekFields.of(Locale.getDefault());
// # may range from 0 ... 54 without overlapping the boundaries of calendar year
int week = newValue.get(fields.weekOfYear());
// # may range from 1 ... 53 with overlapping
int weekBased = newValue.get(fields.weekOfWeekBasedYear());
LOG.info("week/Based " + week + "/" + weekBased);
});
To see the difference, choose f.i. January 2012 (in locales that start a week at Monday). Which one to actually use, depends on context - the picker itself uses weekOfYear (if showWeekNumbers is enabled)
You can also use the DateTimeFormatter, looks easier for me :
LocalDate date = LocalDate.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("w");
int week = Integer.parseInt(date.format(dtf));