Reputation: 2876
I am trying to write an algorithm that based on a number from 1 to 31 (a day in the month) it will return the milliseconds of the moment before that day/number. The algorithm will always use today date.
Examples: Input: 5 Output: 1601845199000 which is 4th October 2020 at 23:59:59
================
Input: 31 Output: 1601499599000 which is 30 September 2020 at 23:59:59
Because the month does not have 31 days, even if 31 is given the algorithm will take the closest possible date.
The same thing goes for February with 29/30
val calendar = Calendar.getInstance()
calendar.set(Calendar.HOUR_OF_DAY, 23)
calendar.set(Calendar.MINUTE, 59)
calendar.set(Calendar.SECOND, 59)
calendar.clear(Calendar.MILLISECOND)
val lastDayNumber = 31
var found = false
while (!found) {
if (calendar.get(Calendar.DAY_OF_MONTH) <= lastDayNumber) {
found = true
}
if (calendar.get(Calendar.DAY_OF_MONTH) < lastDayNumber) {
calendar.add(Calendar.DATE, 1)
}
}
The problem is that calendar.add(Calendar.DATE, 1)
is not working at all.
This method should find the day before the number given in the future. Am I missing something? I've read the documentation and other StackOverflow answers and I still can't get it.
Edit1: I also tried with calendar.add(Calendar.DAY_OF_MONTH, 1)
and also +1, however, -1 is working perfectly fine.
Edit2:
Looking at GregorianCalendar.java
I see the following:
// Handle week, day and AM_PM fields which involves
// time zone offset change adjustment. Convert the
// given amount to the number of days.
case WEEK_OF_YEAR:
case WEEK_OF_MONTH:
case DAY_OF_WEEK_IN_MONTH:
delta *= 7;
break;
case DAY_OF_MONTH: // synonym of DATE
case DAY_OF_YEAR:
case DAY_OF_WEEK:
break;
If I take for example WEEL_OF_YEAR
, the same code will run also for week_of_month and day_of_week_in_month, because there is no break all 3 fields run through the same code.
However, I see absolutely no code to run on DAY_OF_MONTH
, DAY_OF_YEAR
and DAY_OF_WEEK
.
P.S. I cannot find DATE
field
==================================================
Using LocalDateTime
fun getLastDayOfMonth(date: LocalDateTime): Long {
val lastDayNumber = lastDay?.filter { it.isDigit() }!!.toInt()
var found = false
while (!found) {
if (date.dayOfMonth <= lastDayNumber) {
found = true
} else {
date.plusDays(1)
}
}
return date.toEpochSecond(ZoneOffset.UTC)
}
date.plusDays(1) is not changing the date variable to be next day ...
Upvotes: 0
Views: 599
Reputation: 18568
You can use java.time
, which provides very handy methods for this.
See this example:
import java.time.LocalDate
fun main() {
val fourDaysAgo = findDate(4)
println(fourDaysAgo)
}
fun findDate(daysAgo: Long): LocalDate {
return LocalDate.now().minusDays(daysAgo)
}
It outputs the date that was 4 days ago (executed today / 2020-09-07)
2020-09-03
LocalDate.now()
calculates the date of today (NOTE: just the date part, no time of day, offset or time zone involved) and returns a LocalDate
which has the method minusDays(long days)
that correctly calculates dates ago by adjusting months and years if necessary.
If you do the following:
println(LocalDate.of(2020, 1, 1).minusDays(1))
the output would be
2019-12-31
If you need a time part as well and maybe even an offset or a time zone to really correctly subtract days, hours, minutes or any unit of time, have a look at the other classes in java.time
...
EDIT after the following comment under your question:
If you enter 7 and today is 7 September you should get 6 October. If you enter 8 you should get 7 September (today)
fun findDate(dayOfMonth: Int): LocalDate {
val now = LocalDate.now()
val currentDayOfMonth = now.getDayOfMonth()
if (currentDayOfMonth <= dayOfMonth) {
// use the current month with the given day of month
return now.withDayOfMonth(dayOfMonth)
// subtract a day (will change the month when dayOfMonth == 1)
.minusDays(1)
// and add a month
.plusMonths(1)
} else {
// otherwise just take the current month with the given day of month
return now.withDayOfMonth(dayOfMonth)
// and subtract a day
.minusDays(1)
}
}
Note: I guessed the case where the input day of month is less than the one of today, because you did not specify any behaviour for it. That case is handled the same way equal days of month (argument and today) are.
Upvotes: 2
Reputation: 86262
Here’s my go. Like deHaar I strongly recommend that you use java.time, the modern Java date and time API, for your date and time work. The Calendar
class that you tried to use is poorly designed and long outdated.
Since you want the time 1 second before midnight, let’s first declare a constant with this value:
private static final Duration timeBeforeStartOfDay = Duration.ofSeconds(1);
Should you prefer one day, it’s easy to change it to, say, 1 minute, 1 millisecond or 1 nanosecond.
There are some corner cases we need to take into account. I think that this code does it:
ZoneId zone = ZoneId.systemDefault();
int dayNumber = 31;
LocalDate today = LocalDate.now(zone);
YearMonth ym = YearMonth.from(today);
if (dayNumber <= today.getDayOfMonth()) {
// Next month
ym = ym.plusMonths(1);
}
ZonedDateTime targetDateTime;
if (dayNumber > ym.lengthOfMonth()) {
// Use end of month
targetDateTime = ym.plusMonths(1)
.atDay(1)
.atStartOfDay(zone)
.minus(timeBeforeStartOfDay);
} else {
// Use day number
targetDateTime = ym.atDay(dayNumber)
.atStartOfDay(zone)
.minus(timeBeforeStartOfDay);
}
long milliseconds = targetDateTime.toInstant().toEpochMilli();
System.out.println("Target date and time: " + targetDateTime);
System.out.println("Target milliseconds: " + milliseconds);
It makes a difference in which time zone we run it. I ran it in Europe/Bucharest time zone since I imagined it could be yours. The output today (September 7) was:
Target date and time: 2020-09-30T23:59:59+03:00[Europe/Bucharest] Target milliseconds: 1601499599000
Link: Oracle tutorial: Date Time explaining how to use java.time.
Upvotes: 1