Consecutive Days mapped from string array of comma separated Integers - java

I have received multiple comma-separated string integers as input, for example, the following strings :
"5,6,0"
"0,1,2"
"1,2,3,4"
Each of these integers is meant to represent a day of the week
0 = Sunday 1 = Monday 2 = Tuesday 3 = Wednesday 4 = Thursday 5 =
Friday 6 = Saturday
In the case of the first string, it would mean that Thursday to Sunday
The second string would be valid from Sunday to Tuesday
The third-string would be valid from Monday to Thursday
Currently, I am using the following
private fun mapOfDays(validDays: String): LinkedHashMap<Int, String>
{
if (!validDays.isBlank())
{
val daysArray = validDays.split("\\s*,\\s*") as Array<String>
var mapDays = LinkedHashMap<Int, String>()
var mapDay = LinkedHashMap<Int, String>()
mapDays[0] = "SUNDAY"
mapDays[1] = "MONDAY"
mapDays[2] = "TUESDAY"
mapDays[3] = "WEDNESDAY"
mapDays[4] = "THURSDAY"
mapDays[5] = "FRIDAY"
mapDays[6] = "SATURDAY"
for (day in daysArray)
{
if (mapDays.containsKey(day.toInt()))
{
mapDay[day.toInt()] = mapDays[day.toInt()]!!
}
}
return mapDay
}
return LinkedHashMap()
}
private fun mappedDays(mapOfDays: LinkedHashMap<Int, String>?): String
{
if (!mapOfDays.isNullOrEmpty())
{
val mapSize = mapOfDays.size
if (mapSize > 6) return "All Day"
if (mapSize > 5) return sixDayString(mapOfDays)
if (mapSize > 4) return fiveDayString(mapOfDays)
if (mapSize > 3) return fourDayString(mapOfDays)
if (mapSize > 2) return threeDayString(mapOfDays)
if (mapSize > 1) return twoDayString(mapOfDays)
if (mapSize > 0) return oneDayString(mapOfDays)
}
return ""
}
private fun twoDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val lastPosition: Int = mapOfDays.keys.toIntArray()[1]
val lastDay = Days.values()[lastPosition]
val firstDay = Days.values()[firstPosition]
return "$firstDay and $lastDay"
}
private fun oneDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
var firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val firstDay = Days.values()[firstPosition]
return "$firstDay"
}
private fun threeDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
val firstDay = Days.values()[firstPosition]
val secondDay = Days.values()[secondPosition]
val lastDay = Days.values()[thirdPosition]
return "$firstDay, $secondDay and $lastDay"
}
private fun fourDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
val fourthPosition: Int = mapOfDays.keys.toIntArray()[3]
val firstDay = Days.values()[firstPosition]
val secondDay = Days.values()[secondPosition]
val thirdDay = Days.values()[thirdPosition]
val lastDay = Days.values()[fourthPosition]
return "$firstDay, $secondDay, $thirdDay and $lastDay"
}
private fun fiveDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
val fourthPosition: Int = mapOfDays.keys.toIntArray()[3]
val fifthPosition: Int = mapOfDays.keys.toIntArray()[4]
val firstDay = Days.values()[firstPosition]
val secondDay = Days.values()[secondPosition]
val thirdDay = Days.values()[thirdPosition]
val fourthDay = Days.values()[fourthPosition]
val lastDay = Days.values()[fifthPosition]
return "$firstDay, $secondDay, $thirdDay, $fourthDay and $lastDay"
}
private fun sixDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
var firstPosition: Int = mapOfDays.keys.toIntArray()[0]
var lastPosition: Int = 0
for (day in mapOfDays.keys)
{
lastPosition = day
}
val lastDay = Days.values()[lastPosition]
val firstDay = Days.values()[firstPosition]
return "$firstDay to $lastDay"
}
}
enum class Days()
{
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
However, my current implementation is on able to tell me which days are included and not able to map out the group of days for example:
If I got "0,1,3,4,5,6" The final string output that I would want to have is the following: Wednesday to Monday
or
"0,1,3,4,5" would lead to the following result: Sunday, Monday, Wednesday to Friday.

package days
import java.lang.IllegalArgumentException
class DaysFactory {
fun dayFromInt(index: Int): Day {
return when (index) {
0 -> Day.Sunday
1 -> Day.Monday
2 -> Day.Tuesday
3 -> Day.Wednesday
4 -> Day.Thursday
5 -> Day.Friday
6 -> Day.Saturday
else -> throw IllegalArgumentException("illigal index :$index")
}
}
enum class Day(val index: Int) {
Sunday(0), Monday(1), Tuesday(2), Wednesday(3), Thursday(4), Friday(5), Saturday(6)
}
}
class DaysRange(val seed: String) {
var stringFormat = ""
private fun getTomorrow(dayIndex: Int): Int {
if (dayIndex != 6) return dayIndex + 1
return 0
}
override fun toString(): String =stringFormat
init {
if (isValidInput(seed)) {
val dayFactory = DaysFactory()
val indexes = seed.split(",").map { it.toInt() }
val days = indexes.map { dayFactory.dayFromInt(it) }
val ranges = splitIndexesToRanges(indexes)
ranges.forEach { range ->
if (range.size > 2) {
stringFormat += "${dayFactory.dayFromInt(range.first())} to ${dayFactory.dayFromInt(range.last())},"
} else
range.forEach {
stringFormat += "${dayFactory.dayFromInt(it)},"
}
}
stringFormat = stringFormat.dropLast(1)
}
}
private fun splitIndexesToRanges(daysRange: List<Int>): ArrayList<List<Int>> {
val result = ArrayList<List<Int>>()
val slicePoint = ArrayList<Int>()
for (i in 0 until daysRange.size - 1) {
if (getTomorrow(daysRange[i]) != daysRange[i + 1]) {
slicePoint.add(i)
}
}
var start = 0
slicePoint.forEach {
result.add(daysRange.slice(start..it))
start = it + 1
}
result.add(daysRange.slice(start until daysRange.size))
return result
}
}
private fun isValidInput(seed: String): Boolean = true
fun main(args: Array<String>) {
val input = listOf(
"0,1,2,4,5,6",
"5,6,0",
"1,2,3,4"
)
input.forEach {
val dr = DaysRange(it)
println(dr)
}
}
example output:
Sunday to Tuesday,Thursday to Saturday
Friday to Sunday
Monday to Thursday

If you can, I would just stick to a given time API (e.g. java.time if you are on Java 8 or joda-time, etc.). The following solution would also work with your enum, but you need to adapt it a bit (i.e. DayOfWeek has getDisplayName and also allows to add single days and always get the next consecutive day).
I split the work into 3 separate tasks.
reading the input into a list of DayOfWeek:
fun readInput(input : String) : List<DayOfWeek> = input.splitToSequence(",")
.map(String::toInt)
.map {
/* your 0 is Sunday which is 7 for DayOfWeek; rest is the same */
if (it == 0) 7 else it
}
.map(DayOfWeek::of)
.toList()
Maybe you want to add .distinct().sorted() to it or want to validate the input beforehand... That depends on what you really want to ensure...
transforming the day of weeks into a list of consecutive days:
fun List<DayOfWeek>.toDayRangeList() : List<DayRange> = fold(mutableListOf<DayRange>()) { consecutiveDaysList, day ->
consecutiveDaysList.apply {
lastOrNull()?.takeIf { it.to + 1 == day }?.apply {
to = day
} ?: add(DayRange(day))
}
}
For this I also introduced a DateRange-class in order to easily mutate the ending date... You can do this also with immutable objects, but I found this way easier. The DateRange also includes some helper methods to easily get the actual date in the form you want (in my example FULL_STANDALONE):
data class DayRange(var from: DayOfWeek, var to: DayOfWeek = from) {
private fun DayOfWeek.toFullString(locale : Locale) = getDisplayName(TextStyle.FULL_STANDALONE, locale)
fun toString(locale : Locale) : String = when (from) {
// TODO add missing locale specific strings!
to -> from.toFullString(locale)
to + 1 -> "All day"
else -> "${from.toFullString(locale)} to ${to.toFullString(locale)}"
}
// just for convenience we use our custom toString-function:
override fun toString() = toString(Locale.getDefault())
}
optionally "flatten" the list, i.e. if the last day and the first are consecutive, merge them into a single range. As we are dealing with DayOfWeek directly we can simply just add another day and compare two days, regardless whether one is the last day of the week or not:
fun List<DayRange>.flatten(): List<DayRange> {
if (size > 1) {
val first = first()
val last = last()
if (last.to + 1 == first.from)
return dropLast(1).drop(1)
.toMutableList()
.apply {
add(DayRange(last.from, first.to))
}
}
return this
}
Putting it all together / demo:
listOf("1", "1,2", "1,0", "1,2,3", "1,2,4,5", "1,2,4,5,0", "1,2,3,4,5,6,0", "2,3,4,5,6,0,1")
.forEach { input ->
print(input)
readInput(input)
.toDayRangeList()
.flatten()
.joinToString(", ")
.also {
println("-> $it")
}
}
which prints the following:
1 -> Monday
1,2 -> Monday to Tuesday
1,0 -> Sunday to Monday
1,2,3 -> Monday to Wednesday
1,2,4,5 -> Monday to Tuesday, Thursday to Friday
1,2,4,5,0 -> Thursday to Friday, Sunday to Tuesday
1,2,3,4,5,6,0 -> All day
2,3,4,5,6,0,1 -> All day

Related

Java Stream API: Change values by criteria

There is Java class:
public class Item {
private String dateModified;
private Integer color;
}
where dateModified in format "hh:mm:ss",
and ArrayList<Item> list which contains 10 elements.
So i want check my list and:
if now() - dateModified > 1 min , then change color to 1
if now() - dateModified > 5 min , then change color to 2
if now() - dateModified > 10 min, then change color to 3
How to implements it with Java Stream API?
UPDATE:
I implemented my task in such code below. It works as expected, but it seems huge and non-elegance.
I forget to say that list should be mutable.
list.stream()
.map(c -> {
if (compareTime(c.getDateModified(), 600)) {
c.setColor(3);
} else if (compareTime(c.getDateModified(), 300)) {
c.setColor(2);
} else if (compareTime(c.getDateModified(), 60)) {
c.setColor(1);
}
return c;
}).collect(Collectors.toList());
private boolean compareTime(String dateModified, Integer delta) {
boolean result = false;
LocalDateTime now = LocalDateTime.now();
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
Integer secondsDateModified = Integer.parseInt(dateModified.substring(0, 2)) * 3600 +
Integer.parseInt(dateModified.substring(3, 5)) * 60 +
Integer.parseInt(dateModified.substring(6, 8)) ;
Integer secondsNow = hour * 3600 + minute * 60 + second ;
Integer delta1 = secondsNow - secondsDateModified;
if ((delta1) > delta) {
result = true;
}
return result;
}
Any suggestions to improve the code are appreciated.
Instead of storing a String as the time, store a LocalTime object. Also, instead of mutating the original item, return the item or a new item with the new color.
public static class Item {
private final LocalTime dateModified;
private final Integer color;
public Item(LocalTime dateModified, Integer color) {
this.dateModified = dateModified;
this.color = color;
}
public Item withColor(int color) {
return new Item(dateModified, color);
}
public LocalTime getDateModified() {
return dateModified;
}
public Integer getColor() {
return color;
}
}
Example
public static void main(String[] args) {
List<Item> items = new ArrayList<>(Arrays.asList(
new Item(LocalTime.parse("10:30:00"), 0),
new Item(LocalTime.parse("10:30:01"), 255)));
LocalTime now = LocalTime.now();
List<Item> modified = items.stream().map(item -> {
long minutes = Duration.between(item.dateModified, LocalTime.now())
.toMinutes();
return minutes >= 1 ?
item.withColor(minutes >= 10 ? 3 : minutes >= 5 ? 2 : 1) : item;
}).collect(Collectors.toList());
}
What about to use separate streams for update each required range of items:
public static void updateColor(List<Item> items) {
final LocalTime now = LocalTime.now();
final Function<Item, Long> getDurationInMinutes = item -> Duration.between(LocalTime.parse(item.dateModified), now).toMinutes()
final Predicate<Item> betweenOneAndFive = item -> {
long duration = getDurationInMinutes.apply(item);
return duration > ONE && duration <= FIVE;
};
final Predicate<Item> betweenFiveAndTen = item -> {
long duration = getDurationInMinutes.apply(item);
return duration > FIVE && duration <= TEN;
};
final Predicate<Item> greaterThanTen = item -> getDurationInMinutes.apply(item) > TEN;
items.stream().filter(betweenOneAndFive).forEach(item -> item.color = 1);
items.stream().filter(betweenFiveAndTen).forEach(item -> item.color = 2);
items.stream().filter(greaterThanTen).forEach(item -> item.color = 3);
}
It is a matter of proper mapping function from difference of minutes to numbers.
items.forEach(item -> item.setColor(((int) Math.floor(differenceInMinutes(item.getDateModified(), now) + 5)) / 5));
Note, that the differenceInMinutes method would return the difference in floating point arithmetic.
The steps taken are:
Find the difference in minutes from the date of the items with now.
Cast the result to an int which will work like Math.floor.
Add 5 to the result.
Divide by 5.
So, for example 1.3 minutes would result in (1+5)/5 which is 1.
9.8 minutes would result in (9+5)/5 which is 2.
And so on.
First, as Jason, said do not mutate yours items inside streams, make copies. (What is the danger of side effects in Java 8 Streams?).
You will need intermediates operations :
long getElapseTimeSinceModification(Item item) {
return ChronoUnit.MINUTES.between(LocalTime.parse(item.dateModified), LocalDate.now());
}
Optional<Integer> getNewColor(long elapseTimeSinceModification) {
if (elapseTimeSinceModification > 10) {
return Optional.of(3);
} else if (elapseTimeSinceModification > 5) {
return Optional.of(2);
} else if (elapseTimeSinceModification > 1) {
return Optional.of(1);
}
return Optional.empty();
}
Item withNewColor(int newColor, Item item) {
Item newTtem = new Item();
newTtem.color = newColor;
newTtem.dateModified = item.dateModified;
return newTtem;
}
and then you can apply them to your stream :
List<Item> itemsWithNewColor = items.stream()
.map(item -> getNewColor(getElapseTimeSinceModification(item))
.map(newColor -> withNewColor(newColor , item))
.orElse(i))
.collect(Collectors.toList());

A more elegant way to iterate through a list to compare 2 elements next to one another

I have a method which works in this way:
Take as an argument 3 params - a list with dates (sorted in ascending order) , interval unit and interval value
Check whether the next element doesn't exceed the previous date (interval). In other words, given the interval of 30 min, prev - 10:00, next 10:29 - iterate further. if next is 10:31 - break it and return the counter of dates in a row.
The code for it is below:
public static void main(String[] args)
{
Date d1 = new Date();
Date d2 = addOrSubtractTimeUnitFromDate(d1, Calendar.MINUTE, 10, true);
Date d3 = addOrSubtractTimeUnitFromDate(d2, Calendar.MINUTE, 10, true);
Date d4 = addOrSubtractTimeUnitFromDate(d3, Calendar.MINUTE, 10, true);
Date d5 = addOrSubtractTimeUnitFromDate(d4, Calendar.MINUTE, 10, true);
Date d6 = addOrSubtractTimeUnitFromDate(d5, Calendar.MINUTE, 10, true);
List<Date> threeDates = new ArrayList<>();
threeDates.add(d1);
threeDates.add(d2);
threeDates.add(d3);
threeDates.add(d4);
threeDates.add(d5);
threeDates.add(d6);
System.out.println(returnDatesInARowCounter(threeDates, Calendar.MINUTE, 30));
}
private static int returnDatesInARowCounter(List<Date> allDates, int intervalBetween2DatesTimeUnit, int intervalValue)
{
int datesInARowCounter = allDates.size() > 0 ? 1 : 0; // esp. this line (in case allDates is empty)
Date lastDate = null;
Date nextDate;
Iterator<Date> iter = allDates.iterator();
while (iter.hasNext())
{
nextDate = iter.next();
if (lastDate != null) // both lastDate и nextDate are initialized now
{
if(isNextIncidentInIntervalWithLastOneOrNot(lastDate, nextDate, intervalBetween2DatesTimeUnit, intervalValue, true))
{
datesInARowCounter += 1;
}
else break;
}
lastDate = nextDate;
}
return datesInARowCounter;
}
public static Date addOrSubtractTimeUnitFromDate(Date dateToAddToOrSubtractFrom, int calendarTimeUnit, int value, boolean isAdd)
{
if(!isAdd)
{
value = -value;
}
Calendar cal = Calendar.getInstance();
cal.setTime(dateToAddToOrSubtractFrom);
cal.add(calendarTimeUnit, value);
return cal.getTime();
}
private static boolean isNextIncidentInIntervalWithLastOneOrNot(Date lastIncidentRegDate, Date nextIncidentRegDate, int intervalTimeUnit, int intervalValue, boolean isBetween)
{
Date currentIncidentPlusInterval = addOrSubtractTimeUnitFromDate(lastIncidentRegDate, intervalTimeUnit, intervalValue, true);
boolean betweenBool = isDateBetween(nextIncidentRegDate, lastIncidentRegDate, currentIncidentPlusInterval);
return isBetween == betweenBool;
}
private static boolean isDateBetween(Date targetDate, Date startDate, Date endDate)
{
return targetDate.compareTo(startDate) >= 0 && targetDate.compareTo(endDate) <= 0;
}
However, the code looks peculiar to me. Is the any way to make it look more readable?
If you are using Java 8 or newer, you can use the java.time-API instead. It's built-in support for "periods of time" makes the actual implementation much simpler.
static int daysInARow(List<Instant> allInstants, Duration maxDifference) {
int counter = allInstants.size() > 0 ? 1 : 0;
Instant previous = allInstants.get(0);
for (int i = 1; i < allInstants.size(); i++) {
Instant current = allInstants.get(i);
if (Duration.between(previous, current).compareTo(maxDifference) > 0)
break;
counter++;
previous = current;
}
return counter;
}
If you're using java.util.Date in other parts of your project, you can easily convert between Instants by using
Date#from(Instant)
and
Date#toInstant()

Java TimeZone and Linux TimeZone Daylight Savings doesnot match

I need to create POSIX format of TimeZone as defined by the following format.
std offset dst [offset],start[/time],end[/time]
For ex for "America/New_York" the POSIX format is
EST+5EDT,M3.2.0/2,M11.1.0/2
Now the value M3.2.0/2 is represented in the form Mm.w.d/t.
This specifies day d of week w of month m. The day d must be between 0 (Sunday) and 6. The week w must be between 1 and 5; week 1 is the first week in which day d occurs, and week 5 specifies the last d day in the month. The month m should be between 1 and 12. I borrowed the above explanation from the following link
http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
So the above example states, the normal offset from UTC is 5 hours; since this is west of the prime meridian, the sign is positive. Summer time begins on March’s second Sunday at 2:00am and ends on November’s first Sunday at 2:00am.
When I check this in Linux timezone file /usr/share/zoneinfo/America/New_York, it matches the above value
EST5EDT,M3.2.0,M11.1.0
However when I construct this in java for timezone "America/New_York" I get the following string
EST-5EDT+1,M2.1.1/2,M10.1.1/2
I constructed the above string by extracting the information from the output of the following code.
TimeZone timezone = TimeZone.getTimeZone("America/New_York");
System.out.println(timezone.toString());
The output is as below
sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]
Notice the values endMonth=10 which should be 11 as compared to Linux output.
Relying on the output of toString is not advisable, since there are no contractual guarantees about its format in either the TimeZone or SimpleTimeZone classes.
Obviously, your month numbers are off by one; the week of the month isn’t quite as simple, since you need to take into account the first full week of the month.
I would use Java’s documented public methods to get the information:
static String posixSpecFor(TimeZone tz) {
Formatter posixSpec = new Formatter();
float offset = (float) tz.getRawOffset() / (1000 * 60 * 60) * -1;
posixSpec.format("%s%s%s",
tz.getDisplayName(false, TimeZone.SHORT),
offset >= 0 ? "+" : "",
new DecimalFormat("0.##").format(offset));
if (tz.observesDaylightTime()) {
posixSpec.format("%s", tz.getDisplayName(true, TimeZone.SHORT));
}
ZoneId zone = tz.toZoneId();
TemporalField weekOfMonth =
WeekFields.of(DayOfWeek.SUNDAY, 7).weekOfMonth();
int thisYear = Year.now(zone).getValue();
List<ZoneOffsetTransitionRule> rules =
zone.getRules().getTransitionRules();
if (rules.size() > 2) {
rules = rules.subList(0, 2);
}
for (ZoneOffsetTransitionRule rule : rules) {
posixSpec.format(",M%d.%d.%d/%s",
rule.getMonth().getValue(),
rule.createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rule.getDayOfWeek().getValue() % 7,
rule.getLocalTime());
}
return posixSpec.toString();
}
Following is the complete code that I am using to construct Posix timezone string
public class PosixTimeZone {
public String toPosixTZ(String timezoneStr) {
TimeZone timezone = TimeZone.getTimeZone(timezoneStr);
sop("timezoneStr", timezoneStr);
String posixTX = "";
PosixTimeZoneData pTZData = new PosixTimeZoneData(timezone);
if (timezone.useDaylightTime()) {
posixTX = getPosixDSString(pTZData);
} else {
posixTX = getPosixString(pTZData);
}
return posixTX;
}
public static void main(String args[]) {
System.out.println("Posix TimeZone is " + new PosixTimeZone().toPosixTZ(args[0]));
}
private void sop(String varname, String meesage) {
System.out.println("**************: " + varname + " = " + meesage);
}
private String getPosixDSString(PosixTimeZoneData pTZData) {
String posixString = "";
if ((pTZData.std != null && !pTZData.std.isEmpty())
&& (pTZData.stdOffset != null)//&& !pTZData.stdOffset.isEmpty())
&& (pTZData.dst != null && !pTZData.dst.isEmpty())
&& (pTZData.dstOffset != null)// && !pTZData.dstOffset.isEmpty())
&& (pTZData.start != null && !pTZData.start.isEmpty())
&& (pTZData.end != null && !pTZData.end.isEmpty())) {
posixString = String.format("%s%s%s%s,%s,%s", pTZData.std, pTZData.stdOffset, pTZData.dst,
pTZData.dstOffset, pTZData.start, pTZData.end);
} else {
sop("Error", "Invalid Parameters");
}
return posixString;
}
private String getPosixString(PosixTimeZoneData pTZData) {
String posixString = "";
if ((pTZData.std != null && !pTZData.std.isEmpty())
&& (pTZData.stdOffset != null && !pTZData.stdOffset.isEmpty())) {
posixString = String.format("%s%s", pTZData.std, pTZData.stdOffset);
} else {
sop("Error", "Invalid Parameters");
}
return posixString;
}
class PosixTimeZoneData {
String std = "";
String stdOffset = "";
String dst = "";
String dstOffset = "";
String start = "";
String end = "";
private PosixTimeZoneData(TimeZone timeZone) {
std = timeZone.getDisplayName(false, TimeZone.SHORT);
int rawOffset = (timeZone.getRawOffset() / 3600000) * -1;
stdOffset = (rawOffset >= 0)
? ((rawOffset == 0) || (rawOffset == 1) ? "" : "+" + rawOffset)
: "" + rawOffset;
if (timeZone.useDaylightTime()) {
dst = timeZone.getDisplayName(true, TimeZone.SHORT);
int dstRawOffset = timeZone.getDSTSavings() / 3600000;
dstOffset = (dstRawOffset >= 0)
? ((dstRawOffset == 0) || (dstRawOffset == 1) ? "" : "+" + dstRawOffset)
: "" + dstRawOffset;
ZoneId zone = timeZone.toZoneId();
TemporalField weekOfMonth
= WeekFields.of(DayOfWeek.SUNDAY, 7).weekOfMonth();
int thisYear = Year.now(zone).getValue();
List<ZoneOffsetTransitionRule> rules
= zone.getRules().getTransitionRules();
if (rules != null && !rules.isEmpty()) {
if (rules.size() > 2) {
rules = rules.subList(0, 2);
}
start = String.format("M%d.%d.%d/%s",
rules.get(0).getMonth().getValue(),
rules.get(0).createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rules.get(0).getDayOfWeek().getValue() % 7,
rules.get(0).getLocalTime().getHour());
end = String.format("M%d.%d.%d/%s",
rules.get(1).getMonth().getValue(),
rules.get(1).createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rules.get(1).getDayOfWeek().getValue() % 7,
rules.get(1).getLocalTime().getHour());
}
}
}
}
}

How to determine a date in between Friday and Sunday of the week at a particular time

I'm trying to check a current date and time is in between Friday 17:42 and Sunday 17:42 of the week with Java.
At the moment I'm doing this with really really bad code block. It was a hurry solution. Now I'm refactoring but I couldn't find any method in joda or etc.
Any ideas?
Thanks
private final Calendar currentDate = Calendar.getInstance();
private final int day = currentDate.get(Calendar.DAY_OF_WEEK);
private final int hour = currentDate.get(Calendar.HOUR_OF_DAY);
private final int minute = currentDate.get(Calendar.MINUTE);
if (day != 1 && day != 6 && day != 7) {
if (combined != 0) {
return badge == 1;
} else {
return badge == product;
}
} else {
if (day == 6 && hour > 16) {
if (hour == 17 && minute < 43) {
if (combined != 0) {
return badge == 1;
} else {
return badge == product;
}
} else {
return badge == 0;
}
} else if (day == 6 && hour < 17) {
if (combined != 0) {
return badge == 1;
} else {
return badge == product;
}
} else if (day == 1 && hour > 16) {
if (hour == 17 && minute < 43) {
return badge == 0;
} else {
if (combined != 0) {
return badge == 1;
} else {
return badge == product;
}
}
} else {
return badge == 0;
}
}
I've used the solution like thiswith the help of #MadProgrammer and #Meno Hochschild
Method:
public static boolean isBetween(LocalDateTime check, LocalDateTime startTime, LocalDateTime endTime) {
return ((check.equals(startTime) || check.isAfter(startTime)) && (check.equals(endTime) || check.isBefore(endTime))); }
Usage:
static LocalDateTime now = LocalDateTime.now();
static LocalDateTime friday = now.with(DayOfWeek.FRIDAY).toLocalDate().atTime(17, 41);
static LocalDateTime sunday = friday.plusDays(2).plusMinutes(1);
if (!isBetween(now, friday, sunday)) { ... }
Thanks again for your efforts.
Date and Calendar have methods that can perform comparisons on other instances of Date/Calendar, equals, before and after
However, I'd encourage the use of Java 8's new Time API
public static boolean isBetween(LocalDateTime check, LocalDateTime startTime, LocalDateTime endTime) {
return ((check.equals(startTime) || check.isAfter(startTime)) &&
(check.equals(endTime) || check.isBefore(endTime)));
}
Which will return true if the supplied LocalDateTime is within the specified range inclusively.
Something like...
LocalDateTime start = LocalDateTime.now();
start = start.withDayOfMonth(26).withHour(17).withMinute(42).withSecond(0).withNano(0);
LocalDateTime end = start.plusDays(2);
LocalDateTime check = LocalDateTime.now();
System.out.println(check + " is within range = " + isBetween(check, start, end));
check = start;
System.out.println(check + " is within range = " + isBetween(check, start, end));
check = end;
System.out.println(check + " is within range = " + isBetween(check, start, end));
check = start.plusDays(1);
System.out.println(check + " is within range = " + isBetween(check, start, end));
check = end.plusMinutes(1);
System.out.println(check + " is within range = " + isBetween(check, start, end));
Which outputs
2015-06-25T18:31:32.969 is within range = false
2015-06-26T17:42 is within range = true
2015-06-28T17:42 is within range = true
2015-06-27T17:42 is within range = true
2015-06-28T17:43 is within range = false
Joda-Time has an Interval class which makes it even eaiser
Interval targetInterval = new Interval(targetStart, targetEnd);
System.out.println("Contains interval = " + interval.contains(targetInterval)
which is demonstrated here
A different approach...
So I was thinking on way home, assuming all you have is the date/time you want to check, how you might determine if the day falls within your range
LocalDateTime now = LocalDateTime.now();
boolean isBetween = false;
switch (now.getDayOfWeek()) {
case FRIDAY:
case SATURDAY:
case SUNDAY:
LocalDateTime lastFriday = getLastFriday(now);
LocalDateTime nextSunday = getNextSunday(now);
isBetween = isBetween(now, lastFriday, nextSunday);
System.out.println(lastFriday + " - " + nextSunday + ": " + end);
break;
}
What this does is checks the dayOfWeek to see if it's within the desired range, if it is, it finds the previous Friday and next Sunday from the specified date and checks to see if it falls between them (see the previous example)
lastFriday and nextSunday simply adds/subtracts a day from the specified date/time until to reaches the desired dayOfWeek, it then seeds the required time constraints
public static LocalDateTime getLastFriday(LocalDateTime anchor) {
LocalDateTime ldt = LocalDateTime.from(anchor);
return ldt.with(DayOfWeek.FRIDAY).withHour(17).withMinute(42).withSecond(0).withNano(0);
}
public static LocalDateTime getNextSunday(LocalDateTime anchor) {
LocalDateTime ldt = LocalDateTime.from(anchor);
return ldt.with(DayOfWeek.SUNDAY).withHour(17).withMinute(42).withSecond(0).withNano(0);
}
With Calendar you can know what DAY_OF_WEEK is the given date, then simply check the hours:
Calendar c = Calendar.getInstance();
int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
// in friday the hour must be greater than 17:42
if (dayOfWeek == 5 && ((hour > 17) || (hour == 17 && minute >= 42)) {
// successss!!
}
// days from 1 to 7... saturday(6) all day
if (dayOfWeek == 6) {
// successss!!
}
// sunday hour must be lower than 17:42
if (dayOfWeek == 7 && ((hour < 17) || (hour == 17 && minute <= 42)) {
// successss!!
}
A better solution using old Java would look like this:
// current timestamp
GregorianCalendar gcal = new GregorianCalendar();
// specify ISO-week (you are searching for friday until sunday in this order)
gcal.setMinimalDaysInFirstWeek(4);
gcal.setFirstDayOfWeek(Calendar.MONDAY);
// sunday at 17:43
GregorianCalendar sunday = (GregorianCalendar) gcal.clone();
sunday.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
sunday.set(Calendar.HOUR_OF_DAY, 17);
sunday.set(Calendar.MINUTE, 43);
sunday.set(Calendar.SECOND, 0);
sunday.set(Calendar.MILLISECOND, 0);
// friday at 17:42
GregorianCalendar friday = (GregorianCalendar) sunday.clone();
friday.add(Calendar.DATE, -2);
friday.add(Calendar.MINUTE, -1);
// logging for test purposes
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
System.out.println(f.format(friday.getTime()));
System.out.println(f.format(gcal.getTime()));
System.out.println(f.format(sunday.getTime()));
// result (assumption: half-open-interval)
boolean withinTimeWindow = !gcal.before(friday) && gcal.before(sunday);
Java-8 offers a shorter approach (assuming ISO-weekmodel):
LocalDateTime now = LocalDateTime.now();
LocalDateTime friday = now.with(DayOfWeek.FRIDAY).toLocalDate().atTime(17, 42);
LocalDateTime sunday = friday.plusDays(2).plusMinutes(1);
boolean withinTimeWindow = !now.isBefore(friday) && now.isBefore(sunday);
Finally your equivalent evaluation can look like this:
if (!withinTimeWindow) {
if (combined != 0) {
return badge == 1;
} else {
return badge == product;
}
} else {
return badge == 0;
}

Count days between two dates with Java 8 while ignoring certain days of week

Below I have 3 methods. The first is very simple. It just counts the total number of days. The second, however, will not only count the days, but will ignore the days of the week that are passed in to the method.
My problem is that the third method is not always correct. It should match the second method. I am guessing it has something to do with leap years, because the difference is usually +=3|4 when it is incorrect.
Additional Info
I am attempting to mock Excel's weekday(serial_number,[return_type]) formula in a way.
serial_number = startDate:Date - daysOfWeekToInclude:Array<Integer>
Example
| A | B | C
+---------+----------------------------------------------------+-----------
1 | Start | =DATE(2014,9,7) | 9/7/2014
2 | End | =DATE(2025,6,13) | 6/13/2025
3 | Include | ={1,2,4,6} (Mon, Tue, Thu, & Sat) | <Disp Only>
4 | Days | =SUM(INT((WEEKDAY($B$1-{1,2,4,6},1)+$B$2-$B$1)/7)) | 2248
There is more information on this function here: How to count / calculate the number of days between two dates in Excel?
Raw Image
Methods
Simply count the number of days between two dates.
public static int simpleDaysBetween(final LocalDate start,
final LocalDate end) {
return (int) ChronoUnit.DAYS.between(start, end);
}
Count number of days, ignoring certain days of week, using a loop.
public static int betterDaysBetween(final LocalDate start,
final LocalDate end, final List<DayOfWeek> ignore) {
int count = 0;
LocalDate curr = start.plusDays(0);
while (curr.isBefore(end)) {
if (!ignore.contains(curr.getDayOfWeek())) {
count++;
}
curr = curr.plusDays(1); // Increment by a day.
}
return count;
}
Count number of days. again but without a loop.
public static int bestDaysBetween(final LocalDate start,
final LocalDate end, final List<DayOfWeek> ignore) {
int days = simpleDaysBetween(start, end);
if (days == 0) {
return 0;
}
if (!ignore.isEmpty()) {
int weeks = days / 7;
int startDay = start.getDayOfWeek().getValue();
int endDay = end.getDayOfWeek().getValue();
int diff = weeks * ignore.size();
for (DayOfWeek day : ignore) {
int currDay = day.getValue();
if (startDay <= currDay) {
diff++;
}
if (endDay > currDay) {
diff++;
}
}
if (endDay > startDay) {
diff -= endDay - startDay;
}
return days - diff;
}
return days;
}
Full code
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
public class DayCounter {
public static void main(String[] args) {
final LocalDate start = LocalDate.of(2014, 9, 7);
final LocalDate end = LocalDate.of(2025, 6, 13);
List<DayOfWeek> ignore = Arrays.asList(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY);
print(start);
print(end);
System.out.println(simpleDaysBetween(start, end));
System.out.println(betterDaysBetween(start, end, ignore));
System.out.println(bestDaysBetween(start, end, ignore));
}
public static void print(LocalDate date) {
System.out.printf("%s -> %s%n", date, date.getDayOfWeek());
}
public static int simpleDaysBetween(final LocalDate start,
final LocalDate end) {
return (int) ChronoUnit.DAYS.between(start, end);
}
public static int betterDaysBetween(final LocalDate start,
final LocalDate end, final List<DayOfWeek> ignore) {
int count = 0;
LocalDate curr = start.plusDays(0);
while (curr.isBefore(end)) {
if (!ignore.contains(curr.getDayOfWeek())) {
count++;
}
curr = curr.plusDays(1); // Increment by a day.
}
return count;
}
public static int bestDaysBetween(final LocalDate start,
final LocalDate end, final List<DayOfWeek> ignore) {
int days = simpleDaysBetween(start, end);
if (days == 0) {
return 0;
}
if (!ignore.isEmpty()) {
int weeks = days / 7;
int startDay = start.getDayOfWeek().getValue();
int endDay = end.getDayOfWeek().getValue();
int diff = weeks * ignore.size();
for (DayOfWeek day : ignore) {
int currDay = day.getValue();
if (startDay <= currDay) {
diff++;
}
if (endDay > currDay) {
diff++;
}
}
if (endDay > startDay) {
diff -= endDay - startDay;
}
return days - diff;
}
return days;
}
}
If we talk about a Java 8 API, why not use the Java 8 features consequently…
static long daysBetween(LocalDate start, LocalDate end, List<DayOfWeek> ignore) {
return Stream.iterate(start, d->d.plusDays(1))
.limit(start.until(end, ChronoUnit.DAYS))
.filter(d->!ignore.contains(d.getDayOfWeek()))
.count();
}
Starting with Java 9, we can use the even simpler
static long daysBetween(LocalDate start, LocalDate end, List<DayOfWeek> ignore) {
return start.datesUntil(end)
.filter(d->!ignore.contains(d.getDayOfWeek()))
.count();
}
Though, it might be worth using a Set with a better-than-linear lookup rather than the List:
static long daysBetween(LocalDate start, LocalDate end, List<DayOfWeek> ignore) {
if(ignore.isEmpty()) return start.until(end, ChronoUnit.DAYS);
EnumSet<DayOfWeek> set = EnumSet.copyOf(ignore);
return start.datesUntil(end)
.filter(d->!ignore.contains(d.getDayOfWeek()))
.count();
}
You may consider changing the parameter to Set<DayOfWeek>, as it is not only more efficient but better suited to the actual use cases. Instead of Arrays.asList(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY), you can pass EnumSet.of(DayOfWeek.SUNDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY), but you can also use constructs like EnumSet.range(DayOfWeek.MONDAY, DayOfWeek.FRIDAY), to denote the typical working days.
You can avoid iterating over all days, but it requires special care about corner cases and hence, thorough testing. And will only pay off for really large ranges. For completeness, this is the optimized variant:
static long daysBetween(LocalDate start, LocalDate end, Set<DayOfWeek> ignore) {
long d1 = start.toEpochDay(), d2 = end.toEpochDay();
if(d1 > d2) throw new IllegalArgumentException();
if(ignore.isEmpty()) return d2 - d1;
int incompleteWeek = 0;
DayOfWeek startDoW = start.getDayOfWeek(), endDoW = end.getDayOfWeek();
if(startDoW != endDoW) {
for(int v1 = startDoW.getValue(), v2 = endDoW.getValue();
v1 != v2 && d1 < d2; v1 = v1%7+1, d1++) {
if(!ignore.contains(DayOfWeek.of(v1))) incompleteWeek++;
}
}
return incompleteWeek + (d2 - d1) * (7 - ignore.size()) / 7;
}
Here, the performance of the ignore set’s lookup doesn’t matter, as we only look up at most six values, however, enforcing a Set, i.e. no duplicates, allows us to use the set’s size to calculate the number of days contained in complete weeks of the range. Complete weeks have the same day of week for the start and (exclusive) end date. So the code only needs to iterate the days, until the start and end day of week match.
You you are using wrong Excel formula. See the section "Using SUM and INT function to count the number of workdays" of the site that you have provided. It is stating the formula as:
=SUM(INT((WEEKDAY(A2-{2,3,4,5,6})+B2-A2)/7))
In Excel, Sunday is 1 and Saturday is 7. The numbers inside the curly braces indicates the day-of-weeks to be included. So for your case the formula will be:
=SUM(INT((WEEKDAY(A2-{2,3,5,7})+B2-A2)/7))
Please see the attached screenshot:
It is returning 2247 as the following code returns:
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class SO25798876 {
public static void main(String[] args) {
String strStartDate = "09/07/2014";
String strEndDate = "06/13/2025";
String pattern = "MM/dd/yyyy";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
LocalDate startDate = LocalDate.parse(strStartDate, formatter);
LocalDate endDate = LocalDate.parse(strEndDate, formatter);
int count = 0;
while(startDate.isBefore(endDate) || startDate.isEqual(endDate)) { // you may want to not to use the isEqual method
DayOfWeek dayOfWeek = startDate.getDayOfWeek();
if(!(dayOfWeek == DayOfWeek.SUNDAY || dayOfWeek == DayOfWeek.WEDNESDAY || dayOfWeek == DayOfWeek.FRIDAY)) {
count++;
}
startDate = startDate.plusDays(1);
}
System.out.println(count);
}
}
You also have mentioned your doubt that the java.time may be not considering leap year, which is wrong, if you add the following piece of code
long year = startDate.getYear();
if(Year.isLeap(year)) {
Month month = startDate.getMonth();
if(month == Month.FEBRUARY && startDate.getDayOfMonth() == 29) {
System.out.println("Calculated 29th Feb for the year: " + year);
}
}
You will see that it is printing:
Calculated 29th Feb for the year: 2016
Calculated 29th Feb for the year: 2020
Calculated 29th Feb for the year: 2024
Lastly the count will be 2247 which matches the Excel result.
Happy coding.
-Tapas

Categories