tl;博士
使用向后移植到早期 Android 的 java.time 类。
问题说明: 从当前日期,移至上一个或同一个星期一,然后移至该日期的基于周的年份的标准 ISO 8601 第 10 周的星期一,添加一周,并生成文本结果日期采用标准 ISO 8601 格式。
org.threeten.bp.LocalDate.now( // Represent a date-only value, without time-of-day and without time zone.
ZoneId.of( "Europe/London" ) // Determining current date requires a time zone. For any given moment, the date and time vary around the globe by zone.
) // Returns a `LocalDate`. Per immutable objects pattern, any further actions generate another object rather than changing (“mutating”) this object.
.with(
TemporalAdjusters.previousOrSame( // Move to another date.
DayOfWeek.MONDAY // Specify desired day-of-week using `DayOfWeek` enum, with seven objects pre-defined for each day-of-week.
)
) // Renders another `LocalDate` object.
.with(
IsoFields.WEEK_OF_WEEK_BASED_YEAR ,
10
)
.plusWeeks( 1 )
.toString()
2018-03-12
简化问题
在追踪神秘或错误的行为时,只需将程序设计到重现问题所需的最低限度。在这种情况下,去掉所谓不相关的 GUI 代码,专注于日期时间类。
就像在科学实验中一样,控制各种变量。在这种情况下,时区和Locale 都会影响Calendar 的行为。一方面,Calendar 中一周的定义因Locale 而异。因此,通过硬编码明确指定这些方面。
设置具体的日期和时间,因为不同区域不同日期的不同时间会影响行为。
Calendar 是一个具有各种实现的超类。如果您期待 GregorianCalendar,请在调试时明确使用它。
因此,请尝试在您的工具场景中运行类似以下的内容来解决您的问题。
TimeZone tz = TimeZone.getTimeZone( "America/Los_Angeles" );
Locale locale = Locale.US;
GregorianCalendar gc = new GregorianCalendar( tz , locale );
gc.set( 2018 , 9- 1 , 3 , 0 , 0 , 0 ); // Subtract 1 from month number to account for nonsensical month numbering used by this terrible class.
gc.set( Calendar.MILLISECOND , 0 ); // Clear fractional second.
System.out.println( "gc (original): " + gc.toString() );
System.out.println( gc.toZonedDateTime() + "\n" ); // Generate a more readable string, using modern java.time classes. Delete this line if running on Android <26.
int week = 10;
gc.set( Calendar.WEEK_OF_YEAR , week );
System.out.println( "gc (week=10): " + gc.toString() );
System.out.println( gc.toZonedDateTime() + "\n" );
int weekAfter = ( week + 1 );
gc.set( Calendar.WEEK_OF_YEAR , weekAfter );
System.out.println( "gc (weekAfter): " + gc.toString() );
System.out.println( gc.toZonedDateTime() + "\n" );
运行时。
gc (原始): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset= -28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3, startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1, minimumDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=8,WEEK_OF_YEAR=36,WEEK_OF_MONTH=2,DAY_OF_MONTH=3,DAY_OF_YEAR=251,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=2,HOUR_OF_DAY= 0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
2018-09-03T00:00-07:00[美国/洛杉矶]
gc (week=10): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles", offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode= 3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek= 1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=8,WEEK_OF_YEAR=10,WEEK_OF_MONTH=2,DAY_OF_MONTH=3,DAY_OF_YEAR=246,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0, HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
2018-03-05T00:00-08:00[美国/洛杉矶]
gc (weekAfter): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset= -28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3, startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1, minimumDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=2,WEEK_OF_YEAR=11,WEEK_OF_MONTH=2,DAY_OF_MONTH=5,DAY_OF_YEAR=64,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY= 0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=0]
2018-03-12T00:00-07:00[美国/洛杉矶]
java.time
真的,您的问题没有实际意义,因为您根本不应该使用糟糕的旧 Calendar 类。它是多年前被现代 java.time 类取代的麻烦的旧日期时间类的一部分。对于早期的 Android,请参阅下方底部的最后一个项目符号。
在Calendar/GregorianCalendar 中,一周的定义因Locale 而不同,而在java.time 中默认情况并非如此,它使用ISO 8601 标准definition of a week。
- 第 1 周是日历年的第一个星期四。
- 星期一是一周的第一天。
- 基于周的一年有 52 周或 53 周。
- 日历的第一天/最后几天可能出现在基于上一周/下一周的年份中。
LocalDate
LocalDate 类表示没有时间和时区的仅日期值。
时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜过后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。
如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将您的 desired/expected time zone 明确指定为参数。
以continent/region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 3-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
如果你想使用 JVM 当前的默认时区,请求它并作为参数传递。如果省略,则隐式应用 JVM 的当前默认值。最好是明确的,因为默认值可能会在任何时候在运行时被 JVM 中任何应用程序的任何线程中的任何代码更改。
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
或者指定一个日期。您可以通过数字设置月份,1 月至 12 月的编号为 1-12。
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
或者,更好的是,使用预定义的Month 枚举对象,一年中的每个月一个。提示:在整个代码库中使用这些 Month 对象,而不是仅仅使用整数,以使您的代码更具自我记录性、确保有效值并提供 type-safety。
LocalDate ld = LocalDate.of( 2018 , Month.SEPTEMBER , 3 ) ;
TemporalAdjuster
要移动到前一个星期一,或者如果已经是星期一,则留在该日期,请使用 TemporalAdjusters 类中提供的 TemporalAdjuster 实现。使用 DayOfWeek 枚举指定所需的星期几。
LocalDate monday = ld.with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) ) ;
IsoFields
java.time 类在数周内的支持有限。使用 IsoFields 类及其常量 WEEK_OF_WEEK_BASED_YEAR 和 WEEK_BASED_YEAR。
LocalDate mondayOfWeekTen = monday.with( IsoFields.WEEK_OF_WEEK_BASED_YEAR , 10 ) ;
ISO 8601
ISO 8601 标准定义了许多有用的实用格式,用于将日期时间值表示为文本。这包括数周。让我们生成这样的文本作为输出。
String weekLaterOutput =
weekLater
.get( IsoFields.WEEK_BASED_YEAR )
+ "-W"
+ String.format( "%02d" , weekLater.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR ) )
+ "-"
+ weekLater.getDayOfWeek().getValue()
; // Generate standard ISO 8601 output. Ex: 2018-W11-1
转储到控制台。
System.out.println("ld.toString(): " + ld);
System.out.println("monday.toString(): " +monday);
System.out.println("weekLater.toString(): " + weekLater);
System.out.println( "weekLaterOutput: " + weekLaterOutput ) ;
运行时。
ld.toString(): 2018-09-03
monday.toString(): 2018-09-03
weekLater.toString(): 2018-03-12
weekLater输出:2018-W11-1
Java(非 Android)提示:如果需要花费数周时间进行大量工作,请考虑添加 ThreeTen-Extra 库以访问其 YearWeek 类。
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。
从哪里获得 java.time 类?