Last active
December 1, 2016 16:25
-
-
Save armand1m/330d2cdba6c9d3c996f0 to your computer and use it in GitHub Desktop.
Calculate between two times and applies some rules when needed. (lunch time, is between work time)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package io.github.armand1m.main; | |
import java.time.DayOfWeek; | |
import java.time.Duration; | |
import java.time.LocalDate; | |
import java.time.LocalDateTime; | |
import java.time.LocalTime; | |
import java.time.ZoneId; | |
import java.time.temporal.ChronoUnit; | |
import java.util.ArrayList; | |
import java.util.Date; | |
import java.util.List; | |
import org.apache.commons.lang3.time.DurationFormatUtils; | |
/** | |
* Classe responsável por fazer o calculo do tempo corrido | |
* e tempo útil percorrido entre duas datas e horários. | |
*/ | |
public class TimeCounter { | |
/** @const Horário de Inicio do Expediente. <br> 8h00m00s */ | |
private static final LocalTime INICIO_EXPEDIENTE = LocalTime.of(8, 0); | |
/** @const Horário de Início do Almoço. <br> 12h00m00s */ | |
private static final LocalTime INICIO_ALMOCO = LocalTime.of(12, 0); | |
/** @const Horário de Fim do Almoço. <br> 13h00m00s */ | |
private static final LocalTime FINAL_ALMOCO = LocalTime.of(13, 0); | |
/** @const Horário de Fim do Expediente. <br> 17h00m00s */ | |
private static final LocalTime FINAL_EXPEDIENTE = LocalTime.of(17, 0); | |
/** @const Dias do Final de Semana. **/ | |
private static final ArrayList<DayOfWeek> FINAL_DE_SEMANA = new ArrayList<DayOfWeek>(); | |
/** @const Formato de Hora */ | |
private static final String HOUR_FORMAT = "HH:mm:ss"; | |
/** @var Lista de Feriados da instância */ | |
private List<LocalDate> FERIADOS = new ArrayList<LocalDate>(); | |
static { | |
FINAL_DE_SEMANA.add(DayOfWeek.SATURDAY); | |
FINAL_DE_SEMANA.add(DayOfWeek.SUNDAY); | |
} | |
/** | |
* Formata um objeto {@link Duration} para uma {@link String} formatada | |
* de acordo com a constante {@link TimeCounter#HOUR_FORMAT}. | |
* | |
* @param {@link Duration} duration | |
* @return {@link String} | |
*/ | |
public static String format(Duration duration) { | |
return DurationFormatUtils.formatDuration(duration.toMillis(), HOUR_FORMAT); | |
} | |
/** | |
* Formata um objeto {@link Duration} para uma {@link String} formatada | |
* de acordo com a constante {@link TimeCounter#HOUR_FORMAT}. | |
* | |
* @param {@link Duration} duration | |
* @return {@link String} | |
*/ | |
public static String format(long durationMillis) { | |
return DurationFormatUtils.formatDuration(durationMillis, HOUR_FORMAT); | |
} | |
/** | |
* Adiciona uma lista de feriados à uma instância de {@link TimeCounter}. | |
* | |
* @param {@link List<LocalDate>} feriados | |
* @return {@link Void} | |
*/ | |
public void addFeriados(List<LocalDate> feriados) { | |
FERIADOS.addAll(feriados); | |
} | |
/** | |
* Retorna uma {@link String} que representa o tempo corrido | |
* no formato definido na constante {@link TimeCounter#HOUR_FORMAT} | |
* entre dois objetos {@link LocalDateTime}. | |
* | |
* @param {@link LocalDateTime} dataInicial | |
* @param {@link LocalDateTime} dataFinal | |
* @return {@link String} | |
*/ | |
public String getTempoCorridoBetween(LocalDateTime dataInicial, LocalDateTime dataFinal) { | |
return TimeCounter.format(Duration.between(dataInicial, dataFinal)); | |
} | |
/** | |
* Retorna uma {@link String} que representa o tempo útil | |
* no formato definido na constante {@link TimeCounter#HOUR_FORMAT} | |
* entre dois objetos {@link LocalDateTime}. | |
* | |
* @param {@link LocalDateTime} dataInicial | |
* @param {@link LocalDateTime} dataFinal | |
* @return {@link String} | |
*/ | |
public String getTempoUtilBetween(LocalDateTime dataInicial, LocalDateTime dataFinal) { | |
return TimeCounter.format(getUsefulDuration(dataInicial, dataFinal)); | |
} | |
/** | |
* Retorna uma {@link String} que representa o tempo corrido | |
* no formato definido na constante {@link TimeCounter#HOUR_FORMAT} | |
* entre dois objetos {@link Date}. | |
* | |
* @param {@link Date} dataInicial | |
* @param {@link Date} dataFinal | |
* @return {@link String} | |
*/ | |
public String getTempoCorridoBetween(Date dataInicial, Date dataFinal) { | |
return this.getTempoCorridoBetween(this.parse(dataInicial), this.parse(dataFinal)); | |
} | |
/** | |
* Retorna uma {@link String} que representa o tempo útil | |
* no formato HH:mm:ss entre dois objetos {@link Date}. | |
* | |
* @param {@link Date} dataInicial | |
* @param {@link Date} dataFinal | |
* @return {@link String} | |
*/ | |
public String getTempoUtilBetween(Date dataInicial, Date dataFinal) { | |
return this.getTempoUtilBetween(this.parse(dataInicial), this.parse(dataFinal)); | |
} | |
/** | |
* Retorna uma {@link Duration} que representa o tempo corrido | |
* entre dois objetos {@link Date}. | |
* | |
* @param {@link Date} dataInicial | |
* @param {@link Date} dataFinal | |
* @return {@link Duration} | |
*/ | |
public Duration getDurationBetween(Date dataInicial, Date dataFinal) { | |
return Duration.between(this.parse(dataInicial), this.parse(dataFinal)); | |
} | |
/** | |
* Retorna uma {@link Duration} que representa o tempo útil | |
* entre dois objetos {@link Date}. | |
* | |
* @param {@link Date} dataInicial | |
* @param {@link Date} dataFinal | |
* @return {@link Duration} | |
*/ | |
public Duration getDurationUtilBetween(Date dataInicial, Date dataFinal) { | |
return this.getUsefulDuration(this.parse(dataInicial), this.parse(dataFinal)); | |
} | |
/** | |
* Retorna um objeto {@link Duration} ajustado | |
* para as condições de dias úteis de trabalho entre dois {@link LocalDateTime} | |
* | |
* @param {@link LocalDateTime} initialDate | |
* @param {@link LocalDateTime} finalDate | |
* @return {@link Duration} | |
*/ | |
private Duration getUsefulDuration(LocalDateTime initialDate, LocalDateTime finalDate) { | |
final long countDays = getCountOfDays(initialDate, finalDate); | |
Duration totalDuration = Duration.ZERO; | |
LocalDate currentDate = initialDate.toLocalDate(); | |
for (int i = 0; i < countDays; i++) { | |
if (mustCountDate(currentDate, initialDate, finalDate)) { | |
totalDuration = totalDuration.plus( | |
getDuration( | |
getAdjustedInitialDateTime(currentDate, initialDate), | |
getAdjustedFinalDateTime(currentDate, finalDate))); | |
} | |
currentDate = currentDate.plusDays(1); | |
} | |
return totalDuration; | |
} | |
/** | |
* Retorna um objeto {@link Duration} com tempo de almoço descontado. | |
* | |
* @param {@link LocalDateTime} start | |
* @param {@link LocalDateTime} end | |
* @return {@link Duration} | |
*/ | |
private Duration getDuration(LocalDateTime start, LocalDateTime end) { | |
return Duration | |
.between(start, end) | |
.minus(getDiscountTime(start, end)); | |
} | |
/** | |
* Retorna um objeto {@link Duration} indicando quanto tempo de almoço | |
* deve ser removido. | |
* | |
* @param {@link LocalDateTime} start | |
* @param {@link LocalDateTime} end | |
* @return {@link Duration} | |
*/ | |
private Duration getDiscountTime(LocalDateTime start, LocalDateTime end) { | |
final LocalTime startTime = start.toLocalTime(); | |
final LocalTime endTime = end.toLocalTime(); | |
if (startTime.isBefore(INICIO_ALMOCO) && endTime.isAfter(FINAL_ALMOCO)) { | |
return Duration.between(INICIO_ALMOCO, FINAL_ALMOCO); | |
} | |
if (startTime.isBefore(INICIO_ALMOCO) && isBetweenLunchTime(endTime)) { | |
return Duration.between(INICIO_ALMOCO, endTime); | |
} | |
if (isBetweenLunchTime(startTime) && endTime.isAfter(FINAL_ALMOCO)) { | |
return Duration.between(startTime, FINAL_ALMOCO); | |
} | |
return Duration.ZERO; | |
} | |
/** | |
* Retorna a data e hora inicial ajustada para | |
* o método {@link TimeCounter#getUsefulDuration} | |
* | |
* @param {@link LocalDate} currentDate | |
* @param {@link LocalDateTime} initialDate | |
* @return {@link LocalDateTime} | |
*/ | |
private LocalDateTime getAdjustedInitialDateTime(LocalDate currentDate, LocalDateTime initialDate) { | |
return mustUseInitialDate(currentDate, initialDate) | |
? initialDate | |
: LocalDateTime.of(currentDate, INICIO_EXPEDIENTE); | |
} | |
/** | |
* Retorna a data e hora final ajustada para | |
* o método {@link TimeCounter#getUsefulDuration} | |
* | |
* @param {@link LocalDate} currentDate | |
* @param {@link LocalDateTime} finalDate | |
* @return {@link LocalDateTime} | |
*/ | |
private LocalDateTime getAdjustedFinalDateTime(LocalDate currentDate, LocalDateTime finalDate) { | |
return mustUseFinalDate(currentDate, finalDate) | |
? finalDate | |
: LocalDateTime.of(currentDate, FINAL_EXPEDIENTE); | |
} | |
/** | |
* Retorna a quantidade de dias entre duas datas e horas. | |
* | |
* @param {@link LocalDateTime} initialDate | |
* @param {@link LocalDateTime} finalDate | |
* @return {@link long} | |
*/ | |
private long getCountOfDays(LocalDateTime initialDate, LocalDateTime finalDate) { | |
long countDays = initialDate | |
.toLocalDate() | |
.until(finalDate.toLocalDate(), ChronoUnit.DAYS); | |
if (!initialDate.toLocalTime().equals(finalDate.toLocalTime())) { | |
countDays += 1; | |
} | |
return countDays; | |
} | |
/** | |
* Retorna um booleano indicando se deve utilizar a data inicial ou não. | |
* <br><br> | |
* Critério baseado no valor de {@link TimeCounter#INICIO_EXPEDIENTE}. | |
* | |
* @param {@link LocalDate} currentDate | |
* @param {@link LocalDateTime} initialDate | |
* @return {@link boolean} | |
*/ | |
private boolean mustUseInitialDate(LocalDate currentDate, LocalDateTime initialDate) { | |
return currentDate.isEqual(initialDate.toLocalDate()) && | |
initialDate.toLocalTime().isAfter(INICIO_EXPEDIENTE); | |
} | |
/** | |
* Retorna um booleano indicando se deve utilizar a data final ou não. | |
* <br><br> | |
* Critério baseado no valor de {@link TimeCounter#FINAL_EXPEDIENTE}. | |
* | |
* @param {@link LocalDate} currentDate | |
* @param {@link LocalDateTime} finalDate | |
* @return {@link boolean} | |
*/ | |
private boolean mustUseFinalDate(LocalDate currentDate, LocalDateTime finalDate) { | |
return currentDate.isEqual(finalDate.toLocalDate()) && | |
finalDate.toLocalTime().isBefore(FINAL_EXPEDIENTE); | |
} | |
/** | |
* Retorna um booleano indicando se deve contar esta data como dia trabalhado ou não. | |
* | |
* @param {@link LocalDate} currentDate | |
* @param {@link LocalDateTime} initialDate | |
* @param {@link LocalDateTime} finalDate | |
* @return {@link boolean} | |
*/ | |
private boolean mustCountDate(LocalDate currentDate, LocalDateTime initialDate, LocalDateTime finalDate) { | |
final LocalTime initialTime = initialDate.toLocalTime(); | |
final LocalTime finalTime = finalDate.toLocalTime(); | |
if (isBetweenLunchTime(initialTime) && isBetweenLunchTime(finalTime)) { | |
return false; | |
} | |
final boolean mustUseInitialDate = mustUseInitialDate(currentDate, initialDate); | |
final boolean mustUseFinalDate = mustUseFinalDate(currentDate, finalDate); | |
final boolean isNotBetweenWorkingHours = | |
(mustUseInitialDate && initialTime.isAfter(FINAL_EXPEDIENTE)) || | |
(mustUseFinalDate && finalTime.isBefore(INICIO_EXPEDIENTE)); | |
final boolean isWeekend = FINAL_DE_SEMANA.contains(currentDate.getDayOfWeek()); | |
final boolean isFeriado = FERIADOS.contains(currentDate); | |
return !(isNotBetweenWorkingHours || isWeekend || isFeriado); | |
} | |
/** | |
* Verifica se um objeto {@link LocalTime} está dentro | |
* do período de almoço definido por {@link TimeCounter#INICIO_ALMOCO} e {@link TimeCounter#FINAL_ALMOCO}. | |
* | |
* @param {@link LocalTime} time | |
* @return {@link boolean} | |
*/ | |
private boolean isBetweenLunchTime(LocalTime time) { | |
return (time.equals(INICIO_ALMOCO) || time.isAfter(INICIO_ALMOCO)) && | |
(time.equals(FINAL_ALMOCO) || time.isBefore(FINAL_ALMOCO)); | |
} | |
/** | |
* Transforma um objeto do tipo {@link Date} | |
* para um objeto do tipo {@link LocalDateTime}. | |
* | |
* @param {@link Date} data | |
* @return {@link LocalDateTime} | |
*/ | |
private LocalDateTime parse(Date data) { | |
return LocalDateTime.ofInstant(data.toInstant(), ZoneId.systemDefault()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment