简体   繁体   English

Java 美国日历的周到日期转换(非 ISO8601)

[英]Java Week to Date conversion for US calendar (non-ISO8601)

I need to convert strings of pattern 'YYYY0ww' to dates - based on US calendar (minimial days in week = 1, first day of the week = Sunday).我需要将模式 'YYYY0ww' 字符串转换为日期 - 基于美国日历(一周中的最少天数 = 1,一周的第一天 = 星期日)。 Examples: 2020051, 2020052,...示例:2020051、2020052、...

Currently I'm using the following approach for building appropriate formatters (using java.time packages):目前我正在使用以下方法来构建适当的格式化程序(使用 java.time 包):

DateTimeFormatter weekFormat = DateTimeFormatterBuilder()
                    .parseDefaulting(ChronoField.DAY_OF_WEEK, WeekFields.SUNDAY_START.getFirstDayOfWeek().getValue())
                    .appendValue(WeekFields.SUNDAY_START.weekBasedYear(), 4)
                    .appendValue(WeekFields.SUNDAY_START.weekOfWeekBasedYear(), 3).parseStrict().toFormatter();

DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyyMMdd").withLocale(Locale.US);

The resolved date for a given week must be based on the Sunday of the given week.给定周的解决日期必须基于给定周的星期日。

It seems there's an obscure bug because one the following tests fails:似乎有一个模糊的错误,因为以下测试之一失败:

TestCase.assertEquals("20201213", LocalDate.parse("2020051", weekFormat).format(dateFormat)); // success
TestCase.assertEquals("20201220", LocalDate.parse("2020052", weekFormat).format(dateFormat)); // success
TestCase.assertEquals("20201227", LocalDate.parse("2020053", weekFormat).format(dateFormat)); // fail - parsed date contains year=2020, month=12, day=20 which would be CW 52/2020.
TestCase.assertEquals("20210103", LocalDate.parse("2021001", weekFormat).format(dateFormat)); // success

According to https://www.calendar-365.com/2020-calendar.html there exists a week 53 in 2020 in the US calendar - so "2020053" should be 20201227.根据https://www.calendar-365.com/2020-calendar.html在美国日历中存在 2020 年第 53 周 - 所以“2020053”应该是 20201227。

I already tried to apply.withResolverStyle(ResolverStyle.LENIENT) on the week formatter (because I noticed in the JDK implementation that strict parsing for some reason clamps sometimes - see WeekFields.ofWeekBasedYear() ).我已经尝试在周格式化程序上使用 apply.withResolverStyle(ResolverStyle.LENIENT) (因为我在 JDK 实现中注意到,由于某种原因,严格解析有时会钳制 - 请参阅 WeekFields.ofWeekBasedYear() )。 This makes the CW 53/2020 case work but then the test for "2021001" fails in a similar manner:这使得 CW 53/2020 案例工作,但随后“2021001”的测试以类似的方式失败:

TestCase.assertEquals("20201213", LocalDate.parse("2020051", weekFormat).format(dateFormat)); // success
TestCase.assertEquals("20201220", LocalDate.parse("2020052", weekFormat).format(dateFormat)); // success
TestCase.assertEquals("20201227", LocalDate.parse("2020053", weekFormat).format(dateFormat)); // success
TestCase.assertEquals("20210103", LocalDate.parse("2021001", weekFormat).format(dateFormat)); // fail - parsed date contains year=2020, month=12, day=27 which would be CW 53/2020.

I'm confused that it's so tricky to get week to date conversion correct for non-ISO8601 calendars - I think this use-case is not very exotic.我很困惑,让非 ISO8601 日历正确的星期到日期转换是如此棘手 - 我认为这个用例不是很奇特。

Would appreciate feedback why my approach does not deliver correct results.感谢反馈为什么我的方法没有提供正确的结果。

Update: after thinking more about this - I think I might have trapped into incorrectly believing that there is a calendar week 53 in US calendars in certain years.更新:在对此进行了更多思考之后-我想我可能误以为在某些年份美国日历中有第 53 个日历周。 At least the linked websites mentioned CW 53. But based on the rule that the first week of the year is the week (starting with Sunday) which covers January 1st... probably means that there's never a calendar week 53 (and some websites got it wrong).至少链接的网站提到了 CW 53。但是根据一年中的第一周是涵盖 1 月 1 日的那一周(从星期日开始)的规则......可能意味着从来没有日历周 53(有些网站得到了错了)。

Posting this answer myself as Ole VV provided the essential analysis but declined to create the answer (explanation in his profile) - so kudos to Ole VV我自己作为Ole VV发布这个答案提供了基本分析,但拒绝创建答案(在他的个人资料中解释) - 所以对 Ole VV 表示敬意

First of all it's essential to be clear about which years have a calendar week 53 for the US calendar.首先,必须清楚哪些年份有美国日历的第 53 个日历周。 As the rules for US calendars have not been spec'ed out clearly as a standard (like ISO8601) the most common rule seems to be that Jan 1st defined the first calendar week (based on weeks starting on Sunday and ending on Saturday).由于美国日历的规则尚未明确规定为标准(如 ISO8601),因此最常见的规则似乎是 1 月 1 日定义了第一个日历周(基于从周日开始到周六结束的周数)。 Note that many publicly available US calendars found on a well-known search engine contain incorrect week indices because of incorrectly determined first calender weeks.请注意,在知名搜索引擎上发现的许多公开可用的美国日历包含不正确的周索引,因为第一个日历周确定不正确。

I wrote a small (unpolished) programm to calculate the weeks based on this:我写了一个小(未完善的)程序来计算周数:

import java.util.HashMap;
import java.util.Map;

import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import junit.framework.TestCase;

public class GenerateCWs_TestCase extends TestCase {

    public void test_generate() {

        int startYear = 2015;
        int EndYear = 2028;

        Map<Integer, LocalDate> cw1StartDates = new HashMap<>();

        DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMdd");

        for (int year = startYear; year <= EndYear + 1; year++) {

            LocalDate firstDayInYear = formatter.parseLocalDate(year + "0101");

            System.out.println("Jan 1st. " + year + " -> day of week: " + firstDayInYear.getDayOfWeek());

            LocalDate sundayOfFirstWeek = firstDayInYear.getDayOfWeek() != 7
                    ? firstDayInYear.minusDays(firstDayInYear.getDayOfWeek() - 1 + 1)
                    : firstDayInYear;

            System.out.println("CW1 starts on: " + formatter.print(sundayOfFirstWeek));

            cw1StartDates.put(year, sundayOfFirstWeek);

        }

        for (int year = startYear; year <= EndYear; year++) {

            LocalDate sundayDateOfWeek = cw1StartDates.get(year);

            int weekIndex = 1;

            System.out.println(String.format("CW %02d/%d - Sunday of Week: %s", weekIndex, year,
                    formatter.print(sundayDateOfWeek)));

            while (sundayDateOfWeek.plusWeeks(1).isBefore(cw1StartDates.get(year + 1))) {

                sundayDateOfWeek = sundayDateOfWeek.plusWeeks(1);
                weekIndex++;

                if (weekIndex <= 2 || weekIndex >= 51)
                    System.out.println(String.format("CW %02d/%d - Sunday of Week: %s", weekIndex, year,
                            formatter.print(sundayDateOfWeek)));

            }
            
            System.out.println();

        }

    }

}

which outputs:输出:

CW 01/2015 - Sunday of Week: 20141228
CW 02/2015 - Sunday of Week: 20150104
CW 51/2015 - Sunday of Week: 20151213
CW 52/2015 - Sunday of Week: 20151220
CW 01/2016 - Sunday of Week: 20151227
CW 02/2016 - Sunday of Week: 20160103
CW 51/2016 - Sunday of Week: 20161211
CW 52/2016 - Sunday of Week: 20161218
CW 53/2016 - Sunday of Week: 20161225
CW 01/2017 - Sunday of Week: 20170101
CW 02/2017 - Sunday of Week: 20170108
CW 51/2017 - Sunday of Week: 20171217
CW 52/2017 - Sunday of Week: 20171224
CW 01/2018 - Sunday of Week: 20171231
CW 02/2018 - Sunday of Week: 20180107
CW 51/2018 - Sunday of Week: 20181216
CW 52/2018 - Sunday of Week: 20181223
CW 01/2019 - Sunday of Week: 20181230
CW 02/2019 - Sunday of Week: 20190106
CW 51/2019 - Sunday of Week: 20191215
CW 52/2019 - Sunday of Week: 20191222
CW 01/2020 - Sunday of Week: 20191229
CW 02/2020 - Sunday of Week: 20200105
CW 51/2020 - Sunday of Week: 20201213
CW 52/2020 - Sunday of Week: 20201220
CW 01/2021 - Sunday of Week: 20201227
CW 02/2021 - Sunday of Week: 20210103
CW 51/2021 - Sunday of Week: 20211212
CW 52/2021 - Sunday of Week: 20211219
CW 01/2022 - Sunday of Week: 20211226
CW 02/2022 - Sunday of Week: 20220102
CW 51/2022 - Sunday of Week: 20221211
CW 52/2022 - Sunday of Week: 20221218
CW 53/2022 - Sunday of Week: 20221225
CW 01/2023 - Sunday of Week: 20230101
CW 02/2023 - Sunday of Week: 20230108
CW 51/2023 - Sunday of Week: 20231217
CW 52/2023 - Sunday of Week: 20231224
CW 01/2024 - Sunday of Week: 20231231
CW 02/2024 - Sunday of Week: 20240107
CW 51/2024 - Sunday of Week: 20241215
CW 52/2024 - Sunday of Week: 20241222
CW 01/2025 - Sunday of Week: 20241229
CW 02/2025 - Sunday of Week: 20250105
CW 51/2025 - Sunday of Week: 20251214
CW 52/2025 - Sunday of Week: 20251221
CW 01/2026 - Sunday of Week: 20251228
CW 02/2026 - Sunday of Week: 20260104
CW 51/2026 - Sunday of Week: 20261213
CW 52/2026 - Sunday of Week: 20261220
CW 01/2027 - Sunday of Week: 20261227
CW 02/2027 - Sunday of Week: 20270103
CW 51/2027 - Sunday of Week: 20271212
CW 52/2027 - Sunday of Week: 20271219
CW 01/2028 - Sunday of Week: 20271226
CW 02/2028 - Sunday of Week: 20280102
CW 51/2028 - Sunday of Week: 20281210
CW 52/2028 - Sunday of Week: 20281217
CW 53/2028 - Sunday of Week: 20281224

So year 2022 has a calendar week 53 in the US calendar.因此,2022 年在美国日历中有第 53 个日历周。 Year 2020 does not have a calendar week 53. The following section is based on trying to calculate the Sunday of year 2020 calendar week 53. 2020 年没有第 53 个日历周。以下部分基于尝试计算 2020 年第 53 个日历周的星期日。

From here the in-depth analysis from Ole VV in the comments applies which describes the very unexpected behaviour of DateTimeFormatter in the java.time package: Parsing a year+week info for CW53 in year 2020 with given first day of the week (Sunday) results in 2020/12/20 which however is calendar week 52 (without any indication of an exceptional situation).从这里适用于评论中 Ole VV 的深入分析,它描述了 DateTimeFormatter 在 java.time package 中非常意外的行为:在给定一周的第一天解析 2020 年 CW53 的年+周信息(周日)结果是 2020/12/20,但是是日历周 52(没有任何异常情况的迹象)。 Programs not aware of this would silently continue processing with the returned calendar date from week 52 although the input was calendar week 53.没有意识到这一点的程序会默默地继续处理从第 52 周返回的日历日期,尽管输入的是第 53 周日历。

The use of ResolverStyle.STRICT does not make any difference in this case.在这种情况下,使用 ResolverStyle.STRICT没有任何区别。

The only protection possible is to calculate a calendar date for a given year+week and then add an additional check by reversing the calculation.唯一可能的保护是计算给定年+周的日历日期,然后通过反转计算添加额外的检查。 If input week and output week are mismatching then the described misbehaviour occurred and special handling in client code is required.如果输入周和 output 周不匹配,则发生所述错误行为,需要在客户端代码中进行特殊处理。

Ole was so kind to report a bug: JDK-8293146: Strict DateTimeFormatter fails to report an invalid week 53 in the Oracle Java Bug Database Ole 好心报告了一个错误: JDK-8293146: Strict DateTimeFormatter failed to report an invalid week 53 in the Oracle Java 错误数据库

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM