将Joda-Time迁移至java.time

发布日期:2021年11月12日
作者:Cameron Gregor
案例研究

将Joda-Time迁移至java.time

发布日期:2021年11月12日
作者:Cameron Gregor
查看资源
查看资源

迁移代码(阅读:遗留代码)并不有趣。它需要大量的计划和努力来使它跨越界限。虽然对开发人员来说,这不是最令人兴奋或最有动力的工作,但它确实需要决心和正确的经验来将遗留代码迁移到新的库版本。Joda-Time到java.time就是这样一个需要缜密计划和执行的迁移。

如果你的Java项目是在Java SE 8之前开始的,并且使用了日期/时间处理,那么它可能使用了Joda-Time--一个优秀的库,并且是SE 8之前处理日期和时间功能的事实标准。如果你的项目仍然使用Joda-Time,但希望迁移到java.time,那么请继续阅读。

Java SE 8的发布包括一个新的和改进的标准日期和时间API,通常被称为java.time(JSR-310)。Joda-Time项目现在建议迁移到java.time(JSR-310)。

虽然java.time(JSR-310)在很大程度上受到了Joda-Time的启发,但它并不向后兼容,概念和术语也发生了变化。这就是为什么从Joda-Time迁移到java.time需要仔细关注你所改变的每一行代码。这可能会很耗时,而且几乎会让你希望有一种更容易的、自动化的迁移方式。

有一种更好的迁移方式,我们使用Sensei ,这是一个IntelliJ插件,可以根据你定义的配方(规则)自动执行代码转换。把你的时间花在定义可重用的配方上,而不是执行重复的迁移任务。这种自动化不仅可以转换你的传统Joda-Time代码,还可以帮助团队在编写新代码时就在IDE中遵循这些准则。

为了帮助你取得先机,我们创建了一个公开的Sensei cookbookStandardization on java.time (JSR-310),其中包括以较少痛苦的方式从Joda-Time迁移到java.time的配方。这是一个不断增长的食谱集,我们将继续扩大,以增加更多的食谱的覆盖面。

下面是一个迁移样本的例子,也许可以帮助你了解Sensei 是如何使迁移遗留代码变得不费力气的。

如何设置java.time分区日期时间的视频

从重复的手工迁移到自动化的代码转换

让我们看一个创建新DateTime的例子,它展示了从Joda-Time向java.time迁移一行代码时的一些隐藏陷阱。然后,我们将看一下我们的Sensei 菜谱,它来自我们的java.time标准化(JSR-310)菜谱,并展示它是如何捕获所有这些信息的,这样,任何开发人员都可以重复使用这种相同的迁移。

在这个例子中,我们要从代表DateTime字段值的7个int参数中构造一个Joda-Time DateTime。

我们如何将其迁移到与java.time相当的地方?

Joda-Time中关于这个构造函数的javadoc说。

使用默认时区的ISOChronology从数据字段值中构建一个实例。

起初,我们可能会认为java.time中有DateTime类,但其实并没有。如果你在谷歌上搜索 "从Joda time迁移到java time",你很可能会找到Stephen Colebourne的博文《从Joda-Time转换到java.time》。

这给了你一个好的开始,并为我们指明了使用java.time.ZonedDateTime或java.time.OffsetDateTime的方向。这里是我们的第一个问题,我应该使用哪一个?根据Stephen的评论,可能是ZonedDateTime。

查阅ZonedDateTimejavadoc,我们根本看不到任何构造函数。回到Stephen的博文,我们再往下看。

构造。Joda-Time有一个构造函数,接受一个Object并进行类型转换。java.time只有工厂方法,所以转换是一个用户问题,尽管为字符串提供了parse()方法。

所以一定有一个静态工厂方法,搜索静态方法,我们找到一个看起来很接近的方法,但并不完全相同。

它有7个int参数,就像我们原来的Joda-Time DateTime构造函数一样,但是如果你不注意,你会错过一个重要的细节。 第7个参数不再代表毫秒,而是期待纳秒,这是因为java.time比Joda-Time提高了精度,测量的是纳秒级的时刻。这是一个重要的细节,你很容易就会错过。此外,这个方法还期望有一个ZoneId,所以它让你想知道为什么你以前不需要,现在又为什么需要。

记得我们原来的构造函数的javadoc提到它将使用默认的时区,也许有一种方法可以获得默认的ZoneId?

ZoneId的javadoc没有告诉我们所列出的任何构造函数,但看一下静态方法,我们可以使用systemDefault()。

现在我们已经解决了ZoneId的问题,我们应该如何处理毫秒到纳秒的转换呢?也许我们可以使用java.util.concurrent.TimeUnit来进行转换。

这个方法返回一个long,而我们的方法期望是一个int,所以现在我们也有一个转换问题要解决。也许我们可以尝试一些简单的方法。一个乘法?

这可以工作,但确实看起来有点不合适。如果你还没有注意到,我们已经花了相当多的时间和精力来迁移一行代码。但你可以想象,我们有很多这样的编辑工作要手工完成,而且没有更好的办法。

然而,如果我们再仔细研究一下java.time API,我们可以发现一个看起来更流畅的解决方案。

尽管ZonedDateTime没有明显的方法来设置毫秒,但可以使用with(TemporalField field, long newValue)方法,使用ChronoField.MILLI_OF_SECOND作为TemporalField。

而java文档中提到,它将为我们进行纳秒级的转换。

当这个字段用于设置一个值时,它的行为应该与设置NANO_OF_SECOND的方式相同,其值乘以1,000,000。

因此,我们可以简单地在工厂方法中指定纳秒为0,然后使用with方法创建一个ZonedDateTime,其中有所有原始值和毫秒。

看看我们的最终结果,看起来我们只改变了一行代码,这并没有真正显示出我们为研究一个迁移所做的努力!

创建一个配方,更快、更容易地进行迁移

Sensei 为我们提供了一个与其他开发者分享这些来之不易的信息的方法。通过创建一个捕捉所有这些要求的配方,它将允许Sensei 用户通过点击鼠标来执行这种迁移。

一个Sensei 的食谱包括3个主要部分。

  • 元数据
  • 搜索
  • 可用的修复方法

让我们看看一个Sensei recipe(也可以看作是YAML recipe),它将帮助我们把这个调用迁移到它的java.time等价物。

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond) 。

元数据部分

元数据部分包含关于配方的信息以及如何使用它。

搜索部分

Sensei 配方的搜索部分指定了这个配方应该适用于哪些代码元素。

search:
instanceCreation:
args:
1:
type: int
2:
type: int
3:
type: int
4:
type: int
5:
type: int
6:
type: int
7:
type: int
argCount:7
type: org.joda.time.DateTime

在这个搜索部分,我们看到,我们是。

  • 搜索一个instanceCreation,即一个构造函数的用法。注意:还有许多其他的搜索目标可用
  • 构造函数应该有7个参数,这是由argCount属性指定的。
  • args1-7应该是int类型的
  • 我们正在搜索org.joda.time.DateTime类型的构造函数。

可用的修复部分

availableFixes部分可以指定一个或多个可以应用于匹配代码元素的修复。每个修复可以有多个动作,在我们的例子中,我们有一个单一的修复,执行两个动作。

  • 修复的名称在 "快速修复 "菜单中显示给用户,并描述了如果用户应用这个快速修复会发生什么。
  • 行动列表显示了这个快速修复将执行哪些行动
  • 重写动作将使用一个mustache模板重写代码元素。它可以利用变量和字符串替换函数。
  • modifyAssignedVariable动作将检查这个构造函数是否被用来为一个变量赋值。如果是这样,这个动作将修改该变量,使其被声明为type指定的类型

使用配方来进行代码转换

在我们的配方写好并启用后,它扫描我们的代码,并突出显示可以应用的部分。

在下面的截图中,我们可以看到目标构造函数已经被Sensei 。悬停在被标记的构造函数上,我们可以看到Recipe shortDescription和Quickfix选项Migrate to java.time.ZonedDateTime

迁移新的日期时间

在我们选择了Migrate to java.time.ZonedDateTime这个快速修复方法后,代码就会根据我们在配方中指定的动作进行转换。

分区的日期时间与年月日小时

一次性迁移和跨团队的统一编码实践--与Sensei

从上面的例子我们可以看出,一行代码的迁移可能涉及到来之不易的知识。Sensei ,可以把这些知识变成可操作的食谱或食谱,在团队中共享。你可以计划一个一次性的迁移冲刺,或者在你遇到Joda-Time代码时,采取对java.time进行增量即时转换的方法。你可以启用/禁用配方,作为一种在逻辑阶段或步骤中进行迁移的方式,甚至,通过Sensei ,扩大或减少扫描的文件范围--这种灵活性使代码迁移不那么痛苦。

库迁移只是Sensei 用来规范你的项目的许多方法中的一个例子。你可以随时注意在拉动请求中或自己编码时经常遇到的反模式或某些手动代码转换。如果你有一套经常被开发人员遗漏的编码准则,那么你可以将这些准则转化为配方--使开发人员能够自信地应用经批准的代码转换。

如果你有任何问题,我们很愿意听到你的意见在Slack上加入我们:sensei-scw.slack.com

查看资源
查看资源

作者

卡梅伦-格雷戈尔

想要更多吗?

在博客上深入了解我们最新的安全编码见解。

我们广泛的资源库旨在增强人类对安全编码技术提升的方法。

查看博客
想要更多吗?

获取关于开发者驱动的安全的最新研究

我们广泛的资源库充满了有用的资源,从白皮书到网络研讨会,让你开始使用开发者驱动的安全编码。现在就去探索它。

资源中心

将Joda-Time迁移至java.time

发布日期:2021年11月12日
作者:Cameron Gregor

迁移代码(阅读:遗留代码)并不有趣。它需要大量的计划和努力来使它跨越界限。虽然对开发人员来说,这不是最令人兴奋或最有动力的工作,但它确实需要决心和正确的经验来将遗留代码迁移到新的库版本。Joda-Time到java.time就是这样一个需要缜密计划和执行的迁移。

如果你的Java项目是在Java SE 8之前开始的,并且使用了日期/时间处理,那么它可能使用了Joda-Time--一个优秀的库,并且是SE 8之前处理日期和时间功能的事实标准。如果你的项目仍然使用Joda-Time,但希望迁移到java.time,那么请继续阅读。

Java SE 8的发布包括一个新的和改进的标准日期和时间API,通常被称为java.time(JSR-310)。Joda-Time项目现在建议迁移到java.time(JSR-310)。

虽然java.time(JSR-310)在很大程度上受到了Joda-Time的启发,但它并不向后兼容,概念和术语也发生了变化。这就是为什么从Joda-Time迁移到java.time需要仔细关注你所改变的每一行代码。这可能会很耗时,而且几乎会让你希望有一种更容易的、自动化的迁移方式。

有一种更好的迁移方式,我们使用Sensei ,这是一个IntelliJ插件,可以根据你定义的配方(规则)自动执行代码转换。把你的时间花在定义可重用的配方上,而不是执行重复的迁移任务。这种自动化不仅可以转换你的传统Joda-Time代码,还可以帮助团队在编写新代码时就在IDE中遵循这些准则。

为了帮助你取得先机,我们创建了一个公开的Sensei cookbookStandardization on java.time (JSR-310),其中包括以较少痛苦的方式从Joda-Time迁移到java.time的配方。这是一个不断增长的食谱集,我们将继续扩大,以增加更多的食谱的覆盖面。

下面是一个迁移样本的例子,也许可以帮助你了解Sensei 是如何使迁移遗留代码变得不费力气的。

如何设置java.time分区日期时间的视频

从重复的手工迁移到自动化的代码转换

让我们看一个创建新DateTime的例子,它展示了从Joda-Time向java.time迁移一行代码时的一些隐藏陷阱。然后,我们将看一下我们的Sensei 菜谱,它来自我们的java.time标准化(JSR-310)菜谱,并展示它是如何捕获所有这些信息的,这样,任何开发人员都可以重复使用这种相同的迁移。

在这个例子中,我们要从代表DateTime字段值的7个int参数中构造一个Joda-Time DateTime。

我们如何将其迁移到与java.time相当的地方?

Joda-Time中关于这个构造函数的javadoc说。

使用默认时区的ISOChronology从数据字段值中构建一个实例。

起初,我们可能会认为java.time中有DateTime类,但其实并没有。如果你在谷歌上搜索 "从Joda time迁移到java time",你很可能会找到Stephen Colebourne的博文《从Joda-Time转换到java.time》。

这给了你一个好的开始,并为我们指明了使用java.time.ZonedDateTime或java.time.OffsetDateTime的方向。这里是我们的第一个问题,我应该使用哪一个?根据Stephen的评论,可能是ZonedDateTime。

查阅ZonedDateTimejavadoc,我们根本看不到任何构造函数。回到Stephen的博文,我们再往下看。

构造。Joda-Time有一个构造函数,接受一个Object并进行类型转换。java.time只有工厂方法,所以转换是一个用户问题,尽管为字符串提供了parse()方法。

所以一定有一个静态工厂方法,搜索静态方法,我们找到一个看起来很接近的方法,但并不完全相同。

它有7个int参数,就像我们原来的Joda-Time DateTime构造函数一样,但是如果你不注意,你会错过一个重要的细节。 第7个参数不再代表毫秒,而是期待纳秒,这是因为java.time比Joda-Time提高了精度,测量的是纳秒级的时刻。这是一个重要的细节,你很容易就会错过。此外,这个方法还期望有一个ZoneId,所以它让你想知道为什么你以前不需要,现在又为什么需要。

记得我们原来的构造函数的javadoc提到它将使用默认的时区,也许有一种方法可以获得默认的ZoneId?

ZoneId的javadoc没有告诉我们所列出的任何构造函数,但看一下静态方法,我们可以使用systemDefault()。

现在我们已经解决了ZoneId的问题,我们应该如何处理毫秒到纳秒的转换呢?也许我们可以使用java.util.concurrent.TimeUnit来进行转换。

这个方法返回一个long,而我们的方法期望是一个int,所以现在我们也有一个转换问题要解决。也许我们可以尝试一些简单的方法。一个乘法?

这可以工作,但确实看起来有点不合适。如果你还没有注意到,我们已经花了相当多的时间和精力来迁移一行代码。但你可以想象,我们有很多这样的编辑工作要手工完成,而且没有更好的办法。

然而,如果我们再仔细研究一下java.time API,我们可以发现一个看起来更流畅的解决方案。

尽管ZonedDateTime没有明显的方法来设置毫秒,但可以使用with(TemporalField field, long newValue)方法,使用ChronoField.MILLI_OF_SECOND作为TemporalField。

而java文档中提到,它将为我们进行纳秒级的转换。

当这个字段用于设置一个值时,它的行为应该与设置NANO_OF_SECOND的方式相同,其值乘以1,000,000。

因此,我们可以简单地在工厂方法中指定纳秒为0,然后使用with方法创建一个ZonedDateTime,其中有所有原始值和毫秒。

看看我们的最终结果,看起来我们只改变了一行代码,这并没有真正显示出我们为研究一个迁移所做的努力!

创建一个配方,更快、更容易地进行迁移

Sensei 为我们提供了一个与其他开发者分享这些来之不易的信息的方法。通过创建一个捕捉所有这些要求的配方,它将允许Sensei 用户通过点击鼠标来执行这种迁移。

一个Sensei 的食谱包括3个主要部分。

  • 元数据
  • 搜索
  • 可用的修复方法

让我们看看一个Sensei recipe(也可以看作是YAML recipe),它将帮助我们把这个调用迁移到它的java.time等价物。

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond) 。

元数据部分

元数据部分包含关于配方的信息以及如何使用它。

搜索部分

Sensei 配方的搜索部分指定了这个配方应该适用于哪些代码元素。

search:
instanceCreation:
args:
1:
type: int
2:
type: int
3:
type: int
4:
type: int
5:
type: int
6:
type: int
7:
type: int
argCount:7
type: org.joda.time.DateTime

在这个搜索部分,我们看到,我们是。

  • 搜索一个instanceCreation,即一个构造函数的用法。注意:还有许多其他的搜索目标可用
  • 构造函数应该有7个参数,这是由argCount属性指定的。
  • args1-7应该是int类型的
  • 我们正在搜索org.joda.time.DateTime类型的构造函数。

可用的修复部分

availableFixes部分可以指定一个或多个可以应用于匹配代码元素的修复。每个修复可以有多个动作,在我们的例子中,我们有一个单一的修复,执行两个动作。

  • 修复的名称在 "快速修复 "菜单中显示给用户,并描述了如果用户应用这个快速修复会发生什么。
  • 行动列表显示了这个快速修复将执行哪些行动
  • 重写动作将使用一个mustache模板重写代码元素。它可以利用变量和字符串替换函数。
  • modifyAssignedVariable动作将检查这个构造函数是否被用来为一个变量赋值。如果是这样,这个动作将修改该变量,使其被声明为type指定的类型

使用配方来进行代码转换

在我们的配方写好并启用后,它扫描我们的代码,并突出显示可以应用的部分。

在下面的截图中,我们可以看到目标构造函数已经被Sensei 。悬停在被标记的构造函数上,我们可以看到Recipe shortDescription和Quickfix选项Migrate to java.time.ZonedDateTime

迁移新的日期时间

在我们选择了Migrate to java.time.ZonedDateTime这个快速修复方法后,代码就会根据我们在配方中指定的动作进行转换。

分区的日期时间与年月日小时

一次性迁移和跨团队的统一编码实践--与Sensei

从上面的例子我们可以看出,一行代码的迁移可能涉及到来之不易的知识。Sensei ,可以把这些知识变成可操作的食谱或食谱,在团队中共享。你可以计划一个一次性的迁移冲刺,或者在你遇到Joda-Time代码时,采取对java.time进行增量即时转换的方法。你可以启用/禁用配方,作为一种在逻辑阶段或步骤中进行迁移的方式,甚至,通过Sensei ,扩大或减少扫描的文件范围--这种灵活性使代码迁移不那么痛苦。

库迁移只是Sensei 用来规范你的项目的许多方法中的一个例子。你可以随时注意在拉动请求中或自己编码时经常遇到的反模式或某些手动代码转换。如果你有一套经常被开发人员遗漏的编码准则,那么你可以将这些准则转化为配方--使开发人员能够自信地应用经批准的代码转换。

如果你有任何问题,我们很愿意听到你的意见在Slack上加入我们:sensei-scw.slack.com

我们希望得到您的许可,向您发送有关我们产品和/或相关安全编码主题的信息。我们将始终以最谨慎的态度对待您的个人资料,绝不会将其出售给其他公司用于营销目的。

提交
要提交表格,请启用 "分析 "cookies。完成后,请随时再次禁用它们。