以更好的方式实现 else-if scala 语句

Implement else-if scala statement in a better way

我在 Scala 中开发一个函数,它获取开始日期、截止日期和时区信息作为参数。该函数必须 return 四分之一标签,如下所示:

2020 Q2

可能会发生不同的情况:

我开始实施 - 暂时忽略了一些要求 -,最终得到了数百万个 if 语句,它们甚至没有按照我想要的方式工作。

当开始日期和截止日期都可用时,它会计算季度和所有内容,以及时区信息。当时区不可用(正确)时它会跳过,但当缺少开始日期或截止日期时它不起作用。

代码:

import java.time.{LocalDateTime,ZoneId}
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit._
import java.time.temporal.IsoFields

object someObject extends Serializable {

  def isEmpty(x: String): Boolean = x == null || Option(x.trim).forall(_.isEmpty)

  def quarterlyFilter(startDate: String, dueDate: String, timeZone: String): String = {

      val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
      val calculatorTZ = ZoneId.of("GMT+4")

    try {
          if(isEmpty(dueDate) == true || dueDate.startsWith("9999")) {
              val start = LocalDateTime.parse(startDate, formatter)
              val incomingTZ = ZoneId.of(timeZone)
              val incomingStart = start.atZone(incomingTZ)

              val calculatedStartDate = incomingStart.withZoneSameInstant(calculatorTZ).toLocalDate

              val quarter = calculatedStartDate.get(IsoFields.QUARTER_OF_YEAR)
              val year = calculatedStartDate.getYear
            println("only start")
              year + " " + quarter

          } else if(isEmpty(startDate) == true) {
            val due = LocalDateTime.parse(dueDate, formatter)

            val incomingTZ = ZoneId.of(timeZone)
            val incomingDue = due.atZone(incomingTZ)

            val calculatedDueDate = incomingDue.withZoneSameInstant(calculatorTZ).toLocalDate

            val quarter = calculatedDueDate.get(IsoFields.QUARTER_OF_YEAR)
            val year = calculatedDueDate.getYear
            println("only due")

            year + " " + quarter
          } else {
            val start = LocalDateTime.parse(startDate, formatter)
            val due = LocalDateTime.parse(dueDate, formatter)

            val incomingTZ = ZoneId.of(timeZone)
            val incomingStart = start.atZone(incomingTZ)
            val incomingDue = due.atZone(incomingTZ)

            val calculatedStartDate = incomingStart.withZoneSameInstant(calculatorTZ).toLocalDate
            val calculatedDueDate = incomingDue.withZoneSameInstant(calculatorTZ).toLocalDate

            val startQuarter = calculatedStartDate.get(IsoFields.QUARTER_OF_YEAR)
            val startYear = calculatedStartDate.getYear
            val dueQuarter = calculatedDueDate.get(IsoFields.QUARTER_OF_YEAR)
            val dueYear = calculatedDueDate.getYear
            println("both")
            startYear + " " + startQuarter + " vs " + dueYear + " " + dueQuarter
          }
      } catch {
            case e: java.time.zone.ZoneRulesException => {
              println("no timeZone info")
              null
            }
          }
       }
  def checkQuarter: (String,String,String) => String = quarterlyFilter
  val getQuarterInfo = udf(checkQuarter)

}

当我运行这个用下面的测试数据:

val startDate = "null"
val currentDate = "2018-09-30 21:59:59"
val timeZone = "Europe/Copenhagen"

someObject.quarterlyFilter(startDate, currentDate, timeZone)

我收到此错误,即使根本不应该进行解析..

java.time.format.DateTimeParseException

你能帮我把这个功能做得更好、更简单吗?提前致谢!

如果我正确理解需求,我会这样做:

val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

def quarterForDate(date: LocalDateTime, zoneId: ZoneId) = {
  val zoned = date.atZone(zoneId)
  s"${zoned.getYear} Q${zoned.get(java.time.temporal.IsoFields.QUARTER_OF_YEAR)}"
}

def calculateQuarter(startDateString: Option[String], dueDateString: Option[String], timeZoneString: Option[String]): Option[String] = {
  val startDateOpt = startDateString.flatMap(s => Try(LocalDateTime.parse(s, formatter)).toOption)
  val dueDateOpt = dueDateString.flatMap(s => Try(LocalDateTime.parse(s, formatter)).toOption)
  val zoneIdOpt = timeZoneString.flatMap(s => Try(ZoneId.of(s)).toOption)

  (startDateOpt, dueDateOpt, zoneIdOpt) match {
    case (Some(startDate), Some(dueDate), Some(zoneId)) =>
      val q1 = quarterForDate(startDate, zoneId)
      val q2 = quarterForDate(dueDate, zoneId)
      if (q1 == q2) Some(q1) else None
    case (Some(startDate), None, Some(zoneId)) =>
      Some(quarterForDate(startDate, zoneId))
    case (None, Some(dueDate), Some(zoneId)) =>
      Some(quarterForDate(dueDate, zoneId))
    case _ =>
      None
  }
}

val startDate = Option("null").map(_.trim).filterNot(_.equalsIgnoreCase("null"))
val currentDate = Option("2018-09-30 21:59:59").map(_.trim).filterNot(_.equalsIgnoreCase("null"))
val timeZone = Option("Europe/Copenhagen").map(_.trim).filterNot(_.equalsIgnoreCase("null"))

calculateQuarter(startDate, currentDate, timeZone)
  • 参数是否为空应该在函数之外处理
  • 因为 invalid 和 missing 的处理方式相同,所以我会坚持使用 Option
  • 可以提取格式逻辑以避免重复
  • 不同的情况可以用模式匹配来处理,让逻辑更清晰

即使我的要求有误,这对您来说应该更容易适应这段代码应该做的事情。

好的,让我们看看我们能用它做什么。

首先让我们防止空 timeZone,因为它很容易短路,我们会很高兴知道我们稍后有一个有效的。这里有两个选择;您可以使用条件 return None(Java 样式),或者您可以将整个方法的其余部分包装在 flatMap 中(这是我的偏好)。我们还将在此处检查 ZoneRulesException 位并将无效时区视为 "no time zone"(可以说是):

Option(timeZone).flatMap(tz => Try(ZoneId.of(tz)).toOption) flatMap { incomingTZ => 
   // rest of method
}

due date is null, calculate quarterly label from start date

due date starts with year 9999, calculate quarterly label from start date

9999?呸。好的,我们可以很好地处理这两种情况:

def parseDate(s: String) = Try(LocalDateTime.parse(startDate, formatter)).toOption
val validDueDate = Option(dueDate).flatMap(parseDate).filter(_.getYear < 9999)

(从技术上讲,您的规范讨论了以 9999 开头的日期字符串,因此我们应该在解析之前 .filterNot(_ startsWith "9999")。但是这种方式感觉更清晰,并且可以更轻松地适应其他基于日期的过滤。)

开始日期的处理类似,只是没有年份过滤:

val validStartDate = Option(startDate).flatMap(parseDate)

现在我们有两个输入选项。我认为案例的模式匹配在这里可能是最简单的,以计算出标签的日期:

val dateForCalc = (validStartDate, validDueDate) match {
    case (Some(sd), Some(dd)) if sd == dd => sd // arbitrary, could pick either
    case (Some(sd), Some(dd)) => throw IllegalArgumentException(s"Start Date $sd != Due Date $dd!")
    case (Some(sd), None)     => sd
    case (None,     Some(dd)) => dd
    case (None,     None)     => return None // or whatever "do nothing" means
}

太好了,现在我们在 dateForCalc 中有了开始日期或截止日期,只需要从中计算出季度标签。在这一点上,我们可以只使用所有现有的 if-else 块的内容:

val incomingDate = dateForCalc.atZone(incomingTZ)

val calculatedDate = incomingDate .withZoneSameInstant(calculatorTZ).toLocalDate

val quarter = calculatedDate.get(IsoFields.QUARTER_OF_YEAR)
val year = calculatedDate.getYear
Some(year + " " + quarter)

我认为这是一个相当不错的方法。我们预先进行验证、解析和日期比较;这样,当所有这些都完成时,您就可以使用 相关且经过验证的 输入,只需执行计算季度和年度的无聊逻辑即可。