Java和多SQL RDBMS之间的一周计算差异

yel*_*ver 5 java sql datetime date week-number

我必须做一些报告,并且必须在Java和MySQL的任何日期计算周,但他们有不同的算法.

首先,请注意,通过一些方法,在某一年结束的某些日子里,是明年的第一周.示例:2012-12-31是2013年的第1周(我不记得确切的年份,但它就是这样).所以我必须计算一个约会的正确的一周

在Java中,它基于2个标准,取决于区域设置:

  • 一周的第一天(周日或周一)
  • 第一周的最短日期(1或4)

    Calendar cal = Calendar.getInstance();
    int javaWeek = cal.get(Calendar.WEEK_OF_YEAR);
    int weekYear = cal.getWeekYear();
    
    Run Code Online (Sandbox Code Playgroud)

在MySQL中,它基于2个标准依赖于模式(0到7):

  • 一周的第一天(周日或周一)(Java中也是如此)
  • 第一周的最小天数为4或第一周包含一周的第一天

    SELECT WEEK('2012-12-12', 1);
    SELECT YEARWEEK('2012-12-12', 1);
    
    Run Code Online (Sandbox Code Playgroud)

在MySQL中,一些模式将计算一周的奇怪结果,例如,一年有0周只有1天,其他几周有整整7天(我认为第1周真的是第一周,第0周是前周或某事).我会忽略这些模式

方案是:我在Java中计算了我的系统区域设置(带有设置:getFirstDayOfWeek()==SUNDAY和的区域设置getMinimalDaysInFirstWeek==1)的日期基础列表的周,我想在MySQL中使用相同的结果进行计算

但是我在MySQL中尝试所有8种模式,没有匹配.我失败了,因为在MySQL中没有第一周最小日的模式计算基础是1(只有一些模式有4),所以我手动setMinimalDaysInFirstWeek(4)和MySQL中的模式6匹配Java中的结果

(我必须编写测试,比较Java和MySQL中2年内每天的结果)

所以我的解决方案是,我必须修复 setMinimalDaysInFirstWeek(4),如果getFirstDayOfWeek()是星期一,我在MySQL中选择模式3,如果 getFirstDayOfWeek()是SUNDAY,我在MySQL中选择模式6.我认为这只是一个临时解决方案而且非常奇怪.

还有一个问题,我真的害怕如果我的系统从MySQL变为其他RDBMS,它将有其他方法而我无法处理所有这些(Oracle中的示例没有WEEK功能).那么有什么方法可以让calc周满足很多RDBMS吗?

我有一个想法是在一个平台(Java或MySQL)中的计算周,然后将结果传递给另一个:

  • 如果从Java传递到MySQL,我将结果传递给查询字符串,或者构建一个包含我的结果的表

  • 如果从MySQL传递给Java,我必须编写一个从Java调用它的过程,并且必须解决问题"找到计算方法满足许多RDBMS的方法"

但我必须计算大量的日期,这些都不能满足我,任何人都有想法解决我的问题?

Bas*_*que 6

ISO 8601

虽然我在几周内没有多少工作经验,但在我看来,最明智的方法是遵循ISO 8601标准.该标准明确定义了周数和周数.

标准说:

  • 周从星期一开始.
  • 星期几编号17.星期一= 1.
  • 第一周包含今年的第一个星期四.
  • 周被编号0153.(没有00)

MySQL中的WEEKOFYEAR()

虽然我不知道MySQL(我是Postgres的人),但我确实阅读了有关日期和时间函数的文档.

WEEK( date, mode )功能的模式3 似乎符合ISO 8601,其中一周的第一天是星期一,星期是1-53,今年有4天或更多天(不是官方定义,而是另一种说'包含第一个星期四'的方式) .

此外,MySQL提供了WEEKOFYEAR( date )专门用作调用WEEK模式3的简写功能.所以我建议你坚持使用WEEKOFYEAR.

时区

时区至关重要.日期由时区决定.虽然蒙特利尔正在享受2012-12-12周三晚午夜前的最后时刻,但在巴黎周四已经到了,日期是13日.宇宙历史上的同一时刻,但不同的日期和时间.

我假设,但不知道,MySQL将日期时间值存储在UTC中.所以我也假设通过不应用任何时区调整来调用他们与周相关的函数在UTC中有效地工作.

我怀疑这可能是你问题的根源.如果您忽略指定JVM的默认时区,则java.util.Calendar类将分配JVM的默认时区.顺便说一下,这是一个重要的教训:始终指定时区而不是依赖隐式默认值.因此,MySQL按UTC计算周年,而日历则按其他时区(巴黎,加尔各答等)计算,显然结果会有所不同.

我的猜测是解决方案是:

  • 使用UTC始终如一地完成Java工作
  • 对MySQL中的日期时间值应用时区调整(如果可能,我不知道).

哪种解决方案最好取决于您的业务政策.有些企业可能希望在其家庭办公室或主要供应商/客户等的时区工作.其他企业,尤其是那些在不同时区担忧的企业选择以UTC格式定义所有内容.

我怀疑,在这个缩小的世界时代,大多数人的长期最明智的选择是始终在UTC工作.但我不是经营贵公司的人.

Joda-Time或java.time

至于Java方面,请避免使用java.util.Date和.Calendar类.众所周知,它们很麻烦.即使太阳/ Oracle已经放弃了他们,新排挤掉他们java.time包的Java 8.该套餐的灵感来自Joda-Time.Joda-Time继续作为一个可行的项目,java.time和Joda-Time各有其优点和缺点.两者都非常支持ISO 8601,并且将标准用于许多默认行为.两者都支持每周.Joda-Time团队要求我们迁移到java.time.

多的java.time功能已被后移植到Java 6和7在ThreeTen-反向移植并且还适于在到Android ThreeTenABP.

请参阅有关java.time主题的Oracle教程.

java.time中的示例代码(自Java 8起)

请参阅对类似问题的回答.

OffsetDateTime类表示在时间轴上的一个点,分辨率为纳秒,调整到一个偏移从-UTC(不是一个完整的时区).

OffsetDateTime twelves = OffsetDateTime.of( 2012, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC );
Run Code Online (Sandbox Code Playgroud)

OffsetDateTime::get方法允许您访问值的任何部分.每个部分都定义为a TemporalField.本IsoFields类提供的实现TemporalField了ISO-8601日历系统特定的.这包括我们需要的两个:

正在使用…

int week = twelves.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = twelves.get ( IsoFields.WEEK_BASED_YEAR );
Run Code Online (Sandbox Code Playgroud)

时区是与UTC的偏移量加上一组用于处理诸如夏令时(DST)等异常的规则.在某些时区,这一天不会开始00:00:00.0.因此,我们通过LocalDate类(仅限日期值)让java.time确定一天中的第一个时刻.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate twelves = LocalDate.of( 2012 , 12 , 12 );
ZonedDateTime twelvesMontreal = twelves.atStartOfDay( zoneId );
int week = twelvesMontreal.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = twelvesMontreal.get ( IsoFields.WEEK_BASED_YEAR );
Run Code Online (Sandbox Code Playgroud)

如果您确定时间和时区无关紧要(请三思而行!),那么您可以LocalDate单独使用,因为它也有一个get方法.

LocalDate twelves = LocalDate.of( 2012 , 12 , 12 );
int week = twelves.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = twelves.get ( IsoFields.WEEK_BASED_YEAR );
Run Code Online (Sandbox Code Playgroud)

Joda-Time 2.3中的示例代码.

该方法getWeekOfWeekYear获取周数.本ISODateTimeFormat类有生产格式化由ISO 8601定义的各种周相关的格式工厂方法.

DateTime twelves = new DateTime( 2012, 12, 12, 0, 0, 0, DateTimeZone.UTC );
int weekOfTwelves = twelves.getWeekOfWeekyear();
Run Code Online (Sandbox Code Playgroud)

创建字符串表示.请注意,withZone如果希望格式化程序将时区调整应用于其字符串生成,则可以附加调用.否则,将使用DateTime指定的时区.

String outputWeek = ISODateTimeFormat.weekyearWeek().print(  twelves );
String outputWeekDate = ISODateTimeFormat.weekDate().print(  twelves );
Run Code Online (Sandbox Code Playgroud)

转储到控制台.

System.out.println( "twelves: " + twelves );
System.out.println( "weekOfTwelves: " + weekOfTwelves );
System.out.println( "outputWeek: " + outputWeek );
System.out.println( "outputWeekDate: " + outputWeekDate );
Run Code Online (Sandbox Code Playgroud)

跑步时

twelves: 2012-12-12T00:00:00.000Z
weekOfTwelves: 50
outputWeek: 2012-W50
outputWeekDate: 2012-W50-3
Run Code Online (Sandbox Code Playgroud)

可移植性

至于移植到MySQL之外的数据库,我认为坚持日益普遍的ISO 8601标准将有助于可移植性.

例如,Postgres 提供了函数 isoyear,weekisodow(星期几),记录为符合ISO 8601.