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

我必须做一些报告,并且必须在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(); 

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

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

     SELECT WEEK('2012-12-12', 1); SELECT YEARWEEK('2012-12-12', 1); 

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

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

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

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

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

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

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

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

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

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

ISO 8601

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

标准说:

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

MySQL中的WEEKOFYEAR()

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

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

此外,MySQL提供了WEEKOFYEAR( date )function,专门用作调用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类。 众所周知,它们很麻烦。 甚至Sun / Oracle已经放弃了它们,用Java 8中的新java.time包取代它们。 该套餐的灵感来自Joda-Time 。 Joda-Time继续作为一个可行的项目,java.time和Joda-Time各有其优点和缺点。 两者都非常支持ISO 8601,并且将标准用于许多默认行为。 两者都支持每周。 Joda-Time团队要求我们迁移到java.time。

许多java.timefunction已经在ThreeTen-Backport中反向移植到Java 6和7,并在ThreeTenABP中进一步适应Android。

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

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

请参阅我对类似问题的回答 。

OffsetDateTime类表示时间轴上的一个点,其分辨率为纳秒 ,调整为与UTC的偏移 (不是全时区)。

 OffsetDateTime twelves = OffsetDateTime.of( 2012, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC ); 

OffsetDateTime::get方法允许您访问值的任何部分。 每个部分都定义为TemporalFieldIsoFields类提供特定于ISO- IsoFields TemporalField实现。 这包括我们需要的两个:

  • IsoFields.WEEK_OF_WEEK_BASED_YEAR
  • IsoFields.WEEK_BASED_YEAR

正在使用…

 int week = twelves.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR ); int weekYear = twelves.get ( IsoFields.WEEK_BASED_YEAR ); 

时区是与UTC的偏移量加上一组用于处理诸如夏令时(DST)等exception的规则。 在某些时区,这一天不会在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 ); 

如果您确定时间和时区无关紧要(请三思而后行!),那么您可以单独使用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 ); 

Joda-Time 2.3中的示例代码。

getWeekOfWeekYear方法获取周数。 ISODateTimeFormat类具有工厂方法,用于为ISO 8601定义的各种周相关格式生成格式化程序。

 DateTime twelves = new DateTime( 2012, 12, 12, 0, 0, 0, DateTimeZone.UTC ); int weekOfTwelves = twelves.getWeekOfWeekyear(); 

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

 String outputWeek = ISODateTimeFormat.weekyearWeek().print( twelves ); String outputWeekDate = ISODateTimeFormat.weekDate().print( twelves ); 

转储到控制台。

 System.out.println( "twelves: " + twelves ); System.out.println( "weekOfTwelves: " + weekOfTwelves ); System.out.println( "outputWeek: " + outputWeek ); System.out.println( "outputWeekDate: " + outputWeekDate ); 

跑步的时候。

 twelves: 2012-12-12T00:00:00.000Z weekOfTwelves: 50 outputWeek: 2012-W50 outputWeekDate: 2012-W50-3 

可移植性

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

例如, Postgres 提供 isoyearweekisodow (星期几) 的函数 ,记录为符合ISO 8601。