【问题标题】:What is a good, simple way to compute ISO 8601 week number?计算 ISO 8601 周数的好方法是什么?
【发布时间】:2008-09-29 01:23:48
【问题描述】:

假设我有一个日期,即年、月和日,作为整数。什么是计算给定日期所在周的ISO 8601week number 的好(正确)、简洁且相当易读的算法?我遇到了一些真正可怕的代码,这让我认为肯定有更好的方法。

我希望在 Java 中执行此操作,但任何类型的面向对象语言的伪代码都可以。

【问题讨论】:

    标签: java date


    【解决方案1】:

    tl;博士

    LocalDate.of( 2015 , 12 , 30 )
             .get ( 
                 IsoFields.WEEK_OF_WEEK_BASED_YEAR 
             )
    

    53

    ……或者……

    org.threeten.extra.YearWeek.from (
        LocalDate.of( 2015 , 12 , 30 )
    )
    

    2015-W53

    java.time

    现在 Java 8 及更高版本的 java.time 框架中内置了对 ISO 8601 week 的支持。避免使用旧的且臭名昭著的 java.util.Date/.Calendar 类,因为它们已被 java.time 取代。

    这些新的 java.time 类包括 LocalDate 用于没有时间或时区的仅日期值。请注意,您必须指定一个时区来确定“今天”,因为世界各地的日期不同时。

    ZoneId zoneId = ZoneId.of ( "America/Montreal" );
    ZonedDateTime now = ZonedDateTime.now ( zoneId );
    

    或者按照问题中的建议指定年、月和日。

    LocalDate localDate = LocalDate.of( year , month , dayOfMonth );
    

    IsoFields 类根据 ISO 8601 标准提供信息,包括以周为单位的一年中的一周。

    int calendarYear = now.getYear();
    int weekNumber = now.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
    int weekYear = now.get ( IsoFields.WEEK_BASED_YEAR ); 
    

    接近一年的开始/结束时,基于周的年份可能与日历年相差 ±1。例如,请注意 2015 年底的公历和 ISO 8601 日历之间的区别:第 52 周和第 1 周变为第 52 周和第 53 周。

    ThreeTen-Extra — YearWeek

    YearWeek 类将基于 ISO 8601 的年份编号作为单个对象表示为周编号。此类位于ThreeTen-Extra 项目中。该项目为 Java 中内置的 java.time 类添加了功能。

    ZoneId zoneId = ZoneId.of ( "America/Montreal" );
    YearWeek yw = YearWeek.now( zoneId ) ;
    

    从日期生成YearWeek

    YearWeek yw = YearWeek.from (
        LocalDate.of( 2015 , 12 , 30 )
    )
    

    这个类可以生成和解析标准 ISO 8601 格式的字符串。

    String output = yw.toString() ;
    

    2015-W53

    YearWeek yw = YearWeek.parse( "2015-W53" ) ;
    

    您可以提取周数或基于周的年数。

    int weekNumber = yw.getWeek() ;
    int weekBasedYearNumber = yw.getYear() ;
    

    您可以通过指定要在该周内找到的所需星期几来生成特定日期 (LocalDate)。要指定星期几,请使用 Java 8 及更高版本中内置的 DayOfWeek 枚举。

    LocalDate ld = yw.atDay( DayOfWeek.WEDNESDAY ) ;
    

    关于java.time

    java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

    Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

    您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

    从哪里获得 java.time 类?

    ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

    【讨论】:

      【解决方案2】:

      我相信您可以使用 Calendar 对象(只需将 FirstDayOfWeek 设置为 Monday 并将 MinimalDaysInFirstWeek 设置为 4 以使其符合 ISO 8601)并调用 get(Calendar.WEEK_OF_YEAR)。

      【讨论】:

      • 你的推理和/或证据是......?
      • 大部分时间都有效,但并非总是如此。问题描述为here(是的,它是.Net-Code,但同样的问题)。有关正确解决方案,请参阅 Basil Bourque answere。
      【解决方案3】:
      /* Build a calendar suitable to extract ISO8601 week numbers
       * (see http://en.wikipedia.org/wiki/ISO_8601_week_number) */
      Calendar calendar = Calendar.getInstance();
      calendar.setMinimalDaysInFirstWeek(4);
      calendar.setFirstDayOfWeek(Calendar.MONDAY);
      
      /* Set date */
      calendar.setTime(date);
      
      /* Get ISO8601 week number */
      calendar.get(Calendar.WEEK_OF_YEAR);
      

      【讨论】:

      • 事实上setMinimalDaysInFirstWeek(4)(至少在年初半周)是标准的默认值,在java中不需要 - 我认为
      • 而不是 calendar.get(Calendar.YEAR) 使用 calendar.getWeekYear() 来获得与 ISO 8601 一致的周年
      • @JoopEggen 有时可能是这样,但并非总是如此,因为GregorianCalendar 是区域敏感的。因此,根据所选的语言环境(或默认语言环境),第一周的最少天数一周的第一天 的值可能会有所不同。 GregorianCalendar 的 Javadoc 提供了一些相关信息。
      • 如何在示例中获取日期?如果只有年、月和日,则不知道时区。
      【解决方案4】:

      joda-time 库具有 ISO8601 日历,并提供以下功能:

      http://joda-time.sourceforge.net/cal_iso.html

      yyyy-Www-dTHH:MM:SS.SSS 这种格式的 ISO8601有以下字段:

      * four digit weekyear, see rules below
      * two digit week of year, from 01 to 53
      * one digit day of week, from 1 to 7 where 1 is Monday and 7 is Sunday
      * two digit hour, from 00 to 23
      * two digit minute, from 00 to 59
      * two digit second, from 00 to 59
      * three decimal places for milliseconds if required
      

      周总是完整的,而 一年的第一周是 包括第一个星期四 年。这个定义可能意味着 一年的第一周开始于 前一年和最后一周 明年完成。这 weekyear 字段被定义为引用 拥有周的年份,可能 与实际年份不同。

      这一切的结果是,您创建了一个 DateTime 对象,并调用了相当混乱(但在逻辑上)名为 getWeekOfWeekyear(),其中 weekyear 是 ISO8601 使用的基于特定周的年份定义。

      总的来说,joda-time 是一个非常有用的 API,我已经完全停止使用 java.util.Calendar 和 java.util.Date,除非我需要与使用它们的 API 交互。

      【讨论】:

        【解决方案5】:

        只需 Java.util.Calendar 即可: 您可以创建一个日历实例并设置一周的第一天 以及第一周的最少天数

        Calendar calendar = Calendar.getInstance();
        calendar.setMinimalDaysInFirstWeek(4);
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.setTime(date);
        
        // Now you are ready to take the week of year.
        calendar.get(Calendar.WEEK_OF_YEAR);
        

        这是由javaDoc提供的

        星期确定与 ISO 8601 标准兼容,当 getFirstDayOfWeek() 是星期一,getMinimalDaysInFirstWeek() 是 4, 在首选标准的语言环境中使用哪些值。 这些值可以通过调用 setFirstDayOfWeek() 和 setMinimalDaysInFirstWeek().

        【讨论】:

        • 对于那些既不能访问 Java 8+ 中的 java.time 框架也不能访问 Joda-Time 库的人来说,这是一个很好的答案。但除此之外,应避免使用与 Java 的最早版本捆绑在一起的旧日期时间类,例如 java.util.Date/.Calendar。旧的类是出了名的麻烦。
        • 如何在示例中获取日期?如果只有年、月和日,则不知道时区。
        【解决方案6】:

        Calendar 类几乎可以工作,但基于 ISO 周的年份与符合“Olson 的时区包”的系统报告的不一致。这个来自 Linux 机器的示例显示了基于周的年份值 (2009) 与实际年份 (2010) 的差异:

        $ TZ=UTC /usr/bin/date --date="2010-01-01 12:34:56" "+%a %b %d %T %Z  %%Y=%Y,%%G=%G  %%W=%W,%%V=%V  %s"
        Fri Jan 01 12:34:56 UTC  %Y=2010,%G=2009  %W=00,%V=53  1262349296
        

        但是 Java 的 Calendar 类仍然报告 2010 年,尽管一年中的星期是正确的。 skaffman 提到的 Joda-Time 类确实可以正确处理这个问题:

        import java.util.Calendar;
        import java.util.TimeZone;
        import org.joda.time.DateTime;
        
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        cal.setTimeInMillis(1262349296 * 1000L);
        cal.setMinimalDaysInFirstWeek(4);
        cal.setFirstDayOfWeek(Calendar.MONDAY);
        System.out.println(cal.get(Calendar.WEEK_OF_YEAR));     // %V
        System.out.println(cal.get(Calendar.YEAR));             // %G
        
        DateTime dt = new DateTime(1262349296 * 1000L);
        System.out.println(dt.getWeekOfWeekyear());             // %V
        System.out.println(dt.getWeekyear());                   // %G
        

        运行该程序显示:

        53 2010 53 2009

        因此,日历中的 ISO 8601 周数是正确的,但基于周的年份不正确。

        strftime(3) 报告的手册页:

          %G     The ISO 8601 week-based year (see NOTES) with century as  a  decimal  number.   The
                 4-digit year corresponding to the ISO week number (see %V).  This has the same for‐
                 mat and value as %Y, except that if the ISO week number belongs to the previous  or
                 next year, that year is used instead. (TZ)
        

        【讨论】:

        • 而不是 cal.get(Calendar.YEAR) 使用 cal.getWeekYear() 以获得与 ISO 8601 一致的周年
        【解决方案7】:

        如果您想走在最前沿,您可以参加由 Stephen Colebourne(Joda Fame 的)领导的 the latest drop of the JSR-310 codebase(日期时间 API)。它是一个流畅的界面,实际上是对 Joda 的自下而上的重新设计。

        【讨论】:

        • 仅供参考,JSR 310 现在在 Java 8 中作为 java.time 包实现。
        【解决方案8】:

        这是相反的:给你一周中的星期一的日期(在 perl 中)

        use POSIX qw(mktime);
        use Time::localtime;
        
        sub monday_of_week {
            my $year=shift;
            my $week=shift;
            my $p_date=shift;
        
            my $seconds_1_jan=mktime(0,0,0,1,0,$year-1900,0,0,0);
            my $t1=localtime($seconds_1_jan);
            my $seconds_for_week;
            if (@$t1[6] < 5) {
        #first of january is a thursday (or below)
                $seconds_for_week=$seconds_1_jan+3600*24*(7*($week-1)-@$t1[6]+1);
            } else {
                $seconds_for_week=$seconds_1_jan+3600*24*(7*($week-1)-@$t1[6]+8);
            }
            my $wt=localtime($seconds_for_week);
            $$p_date=sprintf("%02d/%02d/%04d",@$wt[3],@$wt[4]+1,@$wt[5]+1900);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-06-26
          • 1970-01-01
          • 2016-05-02
          • 2023-03-06
          • 1970-01-01
          相关资源
          最近更新 更多