Skip to content

Instantly share code, notes, and snippets.

@armand1m
Last active December 1, 2016 16:25
Show Gist options
  • Save armand1m/330d2cdba6c9d3c996f0 to your computer and use it in GitHub Desktop.
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)
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