Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I need to schedule tasks based on times and days the user inputs. These tasks repeat every week, and based on checkbox values I need to set them enabled on those days.
For example, it's Wednesday the sixth 15:40 UTC+2 at the moment. If the user wants to schedule a task every Wednesday at 12:00, I want to get the time in milliseconds on the thirteenth of November at 12:00. If the task is set to be scheduled at 16:00 every Wednesday, I want the time today. Task scheduled to run on every Thursday results in the millisecond representation of tomorrow. So, basically the closest date that is coming. How do I implement this in Java?
The deprecated Date.getDay() function explains how to do this using Calendar. (Date still works if you really want to use it despite being deprecated).
Calendar.get(Calendar.DAY_OF_WEEK);
Process-wise, you would have a class for storing the event's day of a week as an int, and the time.
Then, you would evaluate today's date and time for the following:
Evaluate whether or not today is the specified day of the week.
If it is, check whether or not the time has already passed.
If it hasn't, schedule it logically for today at that time.
If it has, add 7 days to the calendar date to get the expected date.
Otherwise, if the scheduled day of the week is before today's day of the week:
Subtract the difference between the two days from 7. (i.e. if target day is Sunday (0) and today is Wednesday (3), 7 - (3 - 0) = 4, therefore add 4 days to today's date to get target date)
If it's after, just calculate the difference between the two days (i.e. if the target day is Saturday (6) and today is Wednesday (3), 6 - 3 = 3, therefore add 3 days to today's date to get the target date).
You may also need to check for DST.
The simplest, and maybe cheekiest, answer is to use Quartz. :)
http://quartz-scheduler.org/
You can of course write your own scheduler, but this is not a trivial task.
Edit
To get the date, you can use the add() method on the calendar.
To get the time in ms, you can use the method getTimeInMillis().
If you want a much easier (and in my humble opinion, much more intuitive) approach you can use the DateTime class from joda-time ( http://www.joda.org/joda-time/ ) which are more elegant, immutable and timezone aware. :)
Good luck.
Thanks for answering. Compass' answer was correct and I created the following implementation in Java:
public static long nextDate(int day, int hour, int minute) {
// Initialize the Calendar objects
Calendar current = Calendar.getInstance();
Calendar target = Calendar.getInstance();
// Fetch the current day of the week.
// Calendar class weekday indexing starts at 1 for Sunday, 2 for Monday etc.
// Change it to start from zero on Monday continueing to six for Sunday
int today = target.get(Calendar.DAY_OF_WEEK) - 2;
if(today == -1) today = 7;
int difference = -1;
if(today <= day) {
// Target date is this week
difference = day - today;
} else {
// Target date is passed already this week.
// Let's get the date next week
difference = 7 - today + day;
}
// Setting the target hour and minute
target.set(Calendar.HOUR_OF_DAY, hour);
target.set(Calendar.MINUTE, minute);
target.set(Calendar.SECOND, 0);
// If difference == 0 (target day is this day), let's check that the time isn't passed today.
// If it has, set difference = 7 to get the date next week
if(difference == 0 && current.getTimeInMillis() > target.getTimeInMillis()) {
difference = 7;
}
// Adding the days to the target Calendar object
target.add(Calendar.DATE, difference);
// Just for debug
System.out.println(target.getTime());
// Return the next suitable datetime in milliseconds
return target.getTimeInMillis();
}
Related
I was studying the old Calendar API to see how bad it was, and I found out that Calendar has a roll method. Unlike the add method, roll does not change the values of bigger calendar fields.
For example, the calendar instance c represents the date 2019-08-31. Calling c.roll(Calendar.MONTH, 13) adds 13 to the month field, but does not change the year, so the result is 2019-09-30. Note that the day of month changes, because it is a smaller field.
Related
I tried to find such a method in the modern java.time API. I thought such a method has to be in LocalDate or LocalDateTime, but I found nothing of the sort.
So I tried to write my own roll method:
public static LocalDateTime roll(LocalDateTime ldt, TemporalField unit, long amount) {
LocalDateTime newLdt = ldt.plus(amount, unit.getBaseUnit());
return ldt.with(unit, newLdt.get(unit));
}
However, this only works for some cases, but not others. For example, it does not work for the case described in the documentation here:
Consider a GregorianCalendar originally set to Sunday June 6, 1999.
Calling roll(Calendar.WEEK_OF_MONTH, -1) sets the calendar to Tuesday
June 1, 1999, whereas calling add(Calendar.WEEK_OF_MONTH, -1) sets the
calendar to Sunday May 30, 1999. This is because the roll rule imposes
an additional constraint: The MONTH must not change when the
WEEK_OF_MONTH is rolled. Taken together with add rule 1, the resultant
date must be between Tuesday June 1 and Saturday June 5. According to
add rule 2, the DAY_OF_WEEK, an invariant when changing the
WEEK_OF_MONTH, is set to Tuesday, the closest possible value to Sunday
(where Sunday is the first day of the week).
My code:
System.out.println(roll(
LocalDate.of(1999, 6, 6).atStartOfDay(),
ChronoField.ALIGNED_WEEK_OF_MONTH, -1
));
outputs 1999-07-04T00:00, whereas using Calendar:
Calendar c = new GregorianCalendar(1999, 5, 6);
c.roll(Calendar.WEEK_OF_MONTH, -1);
System.out.println(c.getTime().toInstant());
outputs 1999-05-31T23:00:00Z, which is 1999-06-01 in my timezone.
What is an equivalent of roll in the java.time API? If there isn't one, how can I write a method to mimic it?
First, I cannot remember having seen any useful application of Calendar.roll. Second, I don’t think that the functionality is very well specified in corner cases. And the corner cases would be the interesting ones. Rolling month by 13 months would not be hard without the rollmethod. It may be that similar observations are the reasons why this functionality is not offered by java.time.
Instead I believe that we would have to resort to more manual ways of rolling. For your first example:
LocalDate date = LocalDate.of(2019, Month.JULY, 22);
int newMonthValue = 1 + (date.getMonthValue() - 1 + 13) % 12;
date = date.with(ChronoField.MONTH_OF_YEAR, newMonthValue);
System.out.println(date);
Output:
2019-08-22
I am using the fact that in the ISO chronology there are always 12 months in the year. Since % always gives a 0-based result, I subtract 1 from the 1-based month value before the modulo operation and add it back in afterwards And I am assuming a positive roll. If the number of months to roll may be negative, it gets slightly more complicated (left to the reader).
For other fields I think that a similar approach will work for most cases: Find the smallest and the largest possible value of the field given the larger fields and do some modulo operation.
It may become a challenge in some cases. For example, when summer time (DST) ends and the clock is turned backward from 3 to 2 AM, so the day is 25 hours long, how would you roll 37 hours from 6 AM? I’m sure it can be done. And I am also sure that the functionality is not built in.
For your example with rolling the week of month, another difference between the old and the modern API comes into play: a GregorianCalendar not only defines a calendar day and time, it also defines a week scheme consisting of a first day of the week and a minimum number of days in the first week. In java.time the week scheme is defined by a WeekFields object instead. So while rolling the week of month may be unambiguous in GregorianCalendar, without knowing the week scheme it isn’t with LocalDate or LocalDateTime. An attempt may be to assume ISO weeks (start on Monday, and the first week is the on that has at least 4 days of the new month in it), but it may not always be what a user had intended.
Week of month and week of year are special since weeks cross month and year boundaries. Here’s my attempt to implement a roll of week of month:
private static LocalDate rollWeekOfMonth(LocalDate date, int amount, WeekFields wf) {
LocalDate firstOfMonth = date.withDayOfMonth(1);
int firstWeekOfMonth = firstOfMonth.get(wf.weekOfMonth());
LocalDate lastOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
int lastWeekOfMonth = lastOfMonth.get(wf.weekOfMonth());
int weekCount = lastWeekOfMonth - firstWeekOfMonth + 1;
int newWeekOfMonth = firstWeekOfMonth
+ (date.get(wf.weekOfMonth()) - firstWeekOfMonth
+ amount % weekCount + weekCount)
% weekCount;
LocalDate result = date.with(wf.weekOfMonth(), newWeekOfMonth);
if (result.isBefore(firstOfMonth)) {
result = firstOfMonth;
} else if (result.isAfter(lastOfMonth)) {
result = lastOfMonth;
}
return result;
}
Try it out:
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.SUNDAY_START));
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.ISO));
Output:
1999-06-01
1999-06-30
Explanation: The documentation you quote assumes that Sunday is the first day of the week (it ends “where Sunday is the first day of the week”; it was probably written in the USA) so there is a week before Sunday June 6. And rolling by -1 week should roll into this week before. My first line of code does that.
In the ISO week scheme, Sunday June 6 belong to the week from Monday May 31 through Sunday June 6, so in June there is no week before this week. Therefore my second line of code rolls into the last week of June, June 28 through July 4. Since we cannot go outside June, June 30 is chosen.
I have not tested whether it behaves the same as GregorianCalendar. For comparison,the GregorianCalendar.roll implementation uses 52 code lines to handle the WEEK_OF_MONTH case, compared to my 20 lines. Either I have left something out of consideration, or java.time once again shows it superiority.
Rather my suggestion for the real world is: make your requirements clear and implement them directly on top of java.time, ignoring how the old API behaved. As an academic exercise, your question is a fun and interesting one.
TL;DR
There is no equivalent.
Think about whether you really need the behavior of roll of java.util.Calendar:
/**
* Adds or subtracts (up/down) a single unit of time on the given time
* field without changing larger fields. For example, to roll the current
* date up by one day, you can achieve it by calling:
* roll(Calendar.DATE, true).
* When rolling on the year or Calendar.YEAR field, it will roll the year
* value in the range between 1 and the value returned by calling
* getMaximum(Calendar.YEAR).
* When rolling on the month or Calendar.MONTH field, other fields like
* date might conflict and, need to be changed. For instance,
* rolling the month on the date 01/31/96 will result in 02/29/96.
* When rolling on the hour-in-day or Calendar.HOUR_OF_DAY field, it will
* roll the hour value in the range between 0 and 23, which is zero-based.
*
* #param field the time field.
* #param up indicates if the value of the specified time field is to be
* rolled up or rolled down. Use true if rolling up, false otherwise.
* #see Calendar#add(int,int)
* #see Calendar#set(int,int)
*/
public void roll(int field, boolean up);
I am running the below code on 6/7/2018 in order to omit weekends from any dates returned. However the code seems to determine the below days as the weekend.
13/7/2018 - Friday & 14/7/2018 - Saturday
rather than
14/7/2018 - Saturday & 15/7/2018 - Sunday
I am updating the field indicated to increase / reduce the amount of days in the future I want to select.
If I input 5 days the date returned is 12/7/2018 and if I input 6 days the date returned is 15/7/2018.
Is there something obvious I am missing, any help would be much appreciated.
Date date=new Date();
Calendar calendar = Calendar.getInstance();
date=calendar.getTime();
SimpleDateFormat s;
s=new SimpleDateFormat("dd/MM/yyyy");
System.out.println(s.format(date));
int days = 5; //I am updating this value to increase and decrease days
for(int i=0;i<days;)
{
calendar.add(Calendar.DAY_OF_MONTH, 1);
//here even sat and sun are added
//but at the end it goes to the correct week day.
//because i is only increased if it is week day
if(calendar.get(Calendar.DAY_OF_WEEK)<=5)
{
i++;
}
}
date=calendar.getTime();
s=new SimpleDateFormat("dd/MM/yyyy");
System.out.println(s.format(date));
DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.UK);
LocalDate date = LocalDate.now(ZoneId.of("Pacific/Truk"));
System.out.println(date.format(dateFormatter));
int days = 5;
int i = 0;
while (i < days) {
date = date.plusDays(1);
DayOfWeek day = date.getDayOfWeek();
if (! day.equals(DayOfWeek.SATURDAY) && ! day.equals(DayOfWeek.SUNDAY)) {
i++;
}
}
System.out.println(date.format(dateFormatter));
Output today (Sunday 8th July):
08/07/2018
13/07/2018
13th July is next Friday, so obviously it didn’t take Friday as weekend.
Is there something obvious I am missing(?)
It don’t think it’s that obvious: The Calendar class numbers the days of the week from 1 for Sunday through 7 for Saturday. This comes from an American understanding of weeks. So when your condition was that the day of week should be less than or equal to 5, you included Sunday (1) through Thursday (5) and filtered out Friday (6) and Saturday.
…if you could point me in the right direction to the documentation…
To find this information in the documentation you would have to look under each constant for day of week, SUNDAY, etc., and there follow the link Constant Field Values. See the links at the bottom of this answer.
The Calendar class has proved poorly designed (despite attempts to fix the problems with Date) and is now long outdated too. Instead I recommend that you use java.time, the modern Java date and time API. Which I of course do in the snippet above.
One of many problems with Calendar is the use of int for day of week (and other items that have names rather than being numbers). It’s unnatural and very easy to confuse. One may say that you reinforced the problem by comparing to 5 rather than to Calendar.FRIDAY, but because of the American numbering the latter wouldn’t have solved your issue either. java.time’s DayOfWeek is an enum and doesn’t invite for comparing using “less than” or “is before” (though you may, and it would work in your case). The code referring to named constants SATURDAY and SUNDAY is not only clearer to read, it is also less error-prone.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of LocalDate and DayOfWeek
Calendar.SUNDAYdocumentation
Constant Field Values documentation
I am writing my stubs in StubbyDB. And asserting the data in functional tests. This is something I am doing in my functional tests to calculate date for assertion (using joda datetime library)
DateTime now = DateTime.now();
DateTime future = now.plusMonths(6);
And this is something I am doing in my stubs;
{{TODAY+6m}}
But I am getting the difference of few days. Is this the bug or am I doing something wrong?
Edit
Consider today is "30 Sept 2016", and I add 5 months to it then
now.plusMonths(5) => 2017-02-28
{{TODAY+5m}} => 2017-03-02
Reason
As per joda-time documentation,
2007-03-31 plus one month cannot result in 2007-04-31, so the day of
month is adjusted to 2007-04-30.
However StubbyDB use javascript based date calculation which adjust date 2007-04-31 to 2007-05-01.
So this is not the bug but this is how these APIs work.
Solution
Found in sample application
use {{JODA_TODAY+6m}} instead of {{TODAY+6m}}
if you start with 30/09/2016 and add five months you get 30/02/2017.
But February only has 28 days.
It looks like Jodatime has "rounded down" to give you the maximum valid date for the month (i.e 28th Feb) whereas the other library/code is treating "30th Feb" as 2nd March (since that is technically two days past the 28th, which the 30th would also be).
Both are valid assumptions for handling dates IMHO and are a good lesson in why date handling is hard. You'll need to be explicit about which convention you want to follow and you may have to code your assertions to follow Jodatime's conventions.
See: DateTime::plusMonths(int)
Returns a copy of this datetime plus the specified number of months.
The calculation will do its best to only change the month field
retaining the same day of month. However, in certain circumstances, it
may be necessary to alter smaller fields. For example, 2007-03-31 plus
one month cannot result in 2007-04-31, so the day of month is adjusted
to 2007-04-30.
So, 30 Sept 2016 + 5 months = 28 Feb 2017 (according to Joda's logic) and it is not a bug
Here is sample code for adding months to given calendar date
public class Demo {
// create a calendar
Calendar cal = Calendar.getInstance()
// print current date
System.out.println("The current date is : " + cal.getTime());
// add 1 months from the calendar
cal.add(Calendar.MONTH, 1);
}
FYR How to add one month to a date and get the same day
I am trying to determine the sequential ordinal number of a weekday in a month in Java. i.e. if a Friday is the first or 3rd friday of a month.
I can not find a simple way after reading all the things I can find on Java Calendar and posts here. One way I can think of is to determine how many days the first week of this month have in this month and then adjust week_of_month based on what day the day in question is. However, it requires a little complicated calculation. Anyone knows a simple solution?
Just take the day of month, subtract 1, divide by 7, then add 1. The first seven days of the month are always the first (Tuesday, Wednesday, ...) whatever day of the week the actual 1st of the month is.
Personally I'd use Joda Time:
public int getWeekOfWeekDay(LocalDate date) {
return ((date.getDayOfMonth() - 1) / 7) + 1;
}
... but you could do the same using Calendar and fetching the value of the Calendar.DAY_OF_MONTH field.
EDIT: Actually, I've just noticed that for a change, java.util.Calendar is actually simpler than Joda Time - there's a particular field for it! All you need is:
int weekOfWeekDay = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
From the docs for DAY_OF_WEEK_IN_MONTH:
Field number for get and set indicating the ordinal number of the day of the week within the current month. Together with the DAY_OF_WEEK field, this uniquely specifies a day within a month. Unlike WEEK_OF_MONTH and WEEK_OF_YEAR, this field's value does not depend on getFirstDayOfWeek() or getMinimalDaysInFirstWeek(). DAY_OF_MONTH 1 through 7 always correspond to DAY_OF_WEEK_IN_MONTH 1; 8 through 14 correspond to DAY_OF_WEEK_IN_MONTH 2, and so on.
I think I'd probably still use the Joda Time version because it's just a much nicer API all round, but if you're forced to use Calendar, at least you can do this in one shot.
I have maths problem ... (at the moment i solved it using manual iteration which is pretty slow) ...
For example if an employee got paid weekly (it can be fortnightly / every 2 weeks and monthly) with certain date (let's call the employee got paid every tuesday and for monthly the employee paid on certain date).
I have date range between 10th August 2009- 31 December 2009, now how to get frequency the employee got paid ?
is it possible to calculate this using jodatime ?
Example to make this question clear:
I have date range between Friday 14 August - Monday 14 Sept 2009 (31 days)
the employee got paid on every Tuesday
so he got paid on 18 & 25 August, 1 & 8 August we got 4 times payment
(frequency)
another example:
with the same date range Friday 14 August - Monday 14 Sept 2009 (31 days)
but different pay date .. for example on Sunday
so he got paid on : 15, 22 & 29 August , 5 & 12 September ... we got 5 times payment.
same date range but different pay day .. will result different.
So my question is, are there any formula to solve this case ?
at the moment I calculate using manual iterator .. which is very slow (because the range could be some years or months)
thank you
ps: I am using groovy .. any solutions using java or groovy or just algorithm are welcome :)
Oftentimes pay periods are on the 15th and the end of every month, so in that case you'd count the number of months and multiply by 2, checking the end conditions (if start is before the 15th, subtract one pay period; if end is after end of the month subtract one pay period).
It's possible to get counts of days, weeks, and months, but you'll have to add in the logic to handle the dodgy end conditions. It's probably not a simple formula, as the case I described demonstrates.
abosolutely, using the Weeks class is very simple:
DateTime start = new LocalDate(2009, 8, 10).toDateTimeAtStartOfDay();
DateTime end = new LocalDate(2009, 12, 31).toDateTimeAtStartOfDay();
int numberOfWeeks = Weeks.weeksBetween(start, end).getWeeks();
this code give 20 as result. It is right?
EDIT
maybe this is better:
DateMidnight start = new DateMidnight(2009, 8, 10);
DateMidnight end = new DateMidnight(2009, 12, 31);
int numberOfWeeks = Weeks.weeksBetween(start, end).getWeeks();
System.out.println(numberOfWeeks);
Subtracting one date from the other to get the "number of days" (or weeks) is generally the wrong way to go for these kinds of calculations. For example, if someone is 365 days old, they are exactly one year old, unless there was a February 29 during that time. In any (modern) 7-day period, there is always exactly one Tuesday; but for 8 days, it's either one or two. The calendar often figures into the calculations.
If they're paid once or twice a month, you do the easy calculation on the whole months -- starting on the first and ending on the last day of the month, which varies -- and then you have to consider partial months at the beginning and/or end. (Don't forget what happens if the 15th or last day of the month falls on a weekend.) If they're paid every one or two weeks, you can sync on a known payday, and then do the simpler math to figure the whole weeks before and/or since. (Don't forget holidays that fall on the payday.)
There are two tricks here: One is that the rules are different depending on the time frame. I mean, if a person is paid once a week, then in 7 days he gets paid once, in 14 days he gets paid twice, etc. But if a person is paid on the 1st and 16th of every month, I can't tell you how many times he was paid in 60 days without knowing what months were included: where they short months or long months?
The second is that you have to worry about the start and end of the time period. If a person is paid every Monday, then the number of times he gets paid in 8 days depends on whether the first day of the 8 is Monday.
Thus, I think you need to have different logic for schedules that are a fixed number of days and those that are tied to months or something else where the intervals can vary.
For the fixed number of days, the problem is fairly simple. The only complexity is if the time frame is not an exact multiple of the interval. So I'd say, find the first date in the interval on which a payday occurs. Then find the number of days between there and the end of the time period, divide by the interval and drop any fractions.
For example: A person is paid every Monday. How many pay days between March 1 and April 12? Find the first Monday in that range. Say it falls on March 4. Then calculate the number of days from March 4 to April 12. That would be 39. 39/7=5 and a fraction. Therefore he gets paid 5 more paychecks, for a total of 6.
For monthly pay, I think you'd have to separate out the first and last month. You could then count the number of months in the middle and multiply by the number of pays per month. Then for the first and last count how many are in them the hard way.
Just got solutions please check if I did something wrong
import org.joda.time.* ;
def start = new Date().parse("dd/MM/yy","14/08/2009");
def end = new Date().parse("dd/MM/yy","14/09/2009");
println("date range ${start} - ${end}");
def diff = end - start ;
println("diff : ${diff} days ");
println("how many weeks : ${diff/7}");
def payDay = 2 ; // Monday = 1 Sunday = 0
def startDay = new DateTime(start).dayOfWeek ; // 5 = Thursday
def startDayDiff = payDay - startDay ;
if(startDay > payDay){
startDayDiff = 7 + payDay - startDay ;
}
// for example if end on Friday (5) while Pay day is day 1 (Monday) then
// make sure end date is on Monday (same week )
// end date = end - ( endDay - payDay)
def endDay = new DateTime(end).dayOfWeek;
println("original end day: ${endDay}");
def endDayDiff = endDay - payDay ;
// otherwise ... if endDay < payDay (for example PayDay = Friday but End day is on Monday)
// end date = end - 7 + payDay
if(endDay < payDay){
endDayDiff = 7 - endDay - payDay ;
}
println("endDayDiff : ${endDayDiff}");
println("startDayDiff: ${startDayDiff}");
def startedOn = new DateTime(start).plusDays(startDayDiff);
println("started on : ${startedOn.toDate()}");
def endOn = new DateTime(end).minusDays(endDayDiff);
println("End on : ${endOn.toDate()}");
println("occurences : ${Weeks.weeksBetween(startedOn,endOn).getWeeks()+1}");
Tested using groovyConsole with Joda Time help .. :)