我正在使用java.util.Calendar通过其set()方法查找给定星期的开始。
这在Android Nougat +上完美运行,但不适用于棉花糖以下的任何Android版本。
我已经在物理设备和仿真器上进行了测试。
我已使用调试器来验证问题出在日历代码上,而不是显示它时遇到了一些问题。
我已经使用Kotlin和Java创建了不同的最小示例,但问题仍然存在。
以下是Kotlin的最小示例,其中TextView显示日期,而Button用于将该日期增加一周:
class MainActivity : AppCompatActivity() {
var week = 10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Set TextView to show the date of the 10th week in 2018.
setCalendarText(week)
// Increase the week on every button click, and show the new date.
button.setOnClickListener { setCalendarText(++week) }
}
/**
* Set the text of a TextView, defined in XML, to the date of
* a given week in 2018.
*/
fun setCalendarText(week: Int) {
val cal = Calendar.getInstance().apply {
firstDayOfWeek = Calendar.MONDAY
set(Calendar.YEAR, 2018)
set(Calendar.WEEK_OF_YEAR, week)
set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 1)
}
textView.text = SimpleDateFormat("dd MMMM yyyy", Locale.UK).format(cal.time)
}
}
按预期方式工作时,活动启动,TextView设置为显示“ 2018年3月5日”。单击按钮后,此值将更改为每连续一周的第一天。
在Android棉花糖及更低版本上:
Calendar.SUNDAY
,则日历可以正确检索当前当前周的最后一天。其他任何星期都无法使用。编辑:我试图创建Java MVCE,该Java MVCE允许您通过运行CalendarTester.test()
来快速检查是否出现基本问题。
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
class CalendarTester {
/**
* Check that the Calendar returns the correct date for
* the start of the 10th week of 2018 instead of returning
* the start of the current week.
*/
public static void test() {
// en_US on my machine, but should probably be en_GB.
String locale = Locale.getDefault().toString();
Log.v("CalendarTester", "The locale is " + locale);
Long startOfTenthWeek = getStartOfGivenWeek(10);
String startOfTenthWeekFormatted = formatDate(startOfTenthWeek);
boolean isCorrect = "05 March 2018".equals(startOfTenthWeekFormatted);
Log.v("CalendarTester", String.format("The calculated date is %s, which is %s",
startOfTenthWeekFormatted, isCorrect ? "CORRECT" : "WRONG"));
}
public static Long getStartOfGivenWeek(int week) {
Calendar cal = Calendar.getInstance();
cal.setFirstDayOfWeek(Calendar.MONDAY);
cal.set(Calendar.YEAR, 2018);
cal.set(Calendar.WEEK_OF_YEAR, week);
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 1);
return cal.getTimeInMillis();
}
public static String formatDate(Long timeInMillis) {
return new SimpleDateFormat("dd MMMM yyyy", Locale.UK).format(timeInMillis);
}
}
答案 0 :(得分:2)
使用回溯到早期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,转换= 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 = 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_OF = 2,HOUR_OF 0,MINUTE = 0,SECOND = 0,MILLISECOND = 0,ZONE_OFFSET = -28800000,DST_OFFSET = 3600000]
2018-09-03T00:00-07:00 [美国/洛杉矶]
gc(周= 10):java.util.GregorianCalendar [time = ?, areFieldsSet = false,areAllFieldsSet = true,lenient = true,zone = sun.util.calendar.ZoneInfo [id =“ America / Los_Angeles”,偏移量= -28800000,dstSavings = 3600000,useDaylight = true,转换= 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,转换= 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 [美国/洛杉矶]
确实,您的问题没有解决,因为您根本不应该使用可怕的旧Calendar
类。它是几年前麻烦的旧日期时间类的一部分,而现代的 java.time 类取代了该类。对于早期的Android,请参阅下面底部的最后一个项目符号。
在Calendar
/ GregorianCalendar
中,一周的定义因Locale
而异,默认情况下,在 java.time 中使用{{ 3}}标准ISO 8601。
LocalDate
definition of a week类表示没有日期和时区的仅日期值。
时区对于确定日期至关重要。在任何给定时刻,日期都会在全球范围内变化。例如,LocalDate
午夜之后的几分钟是新的一天,而Paris France仍然是“昨天”。
如果未指定时区,则JVM隐式应用其当前的默认时区。该默认值可能在运行时(!)期间Montréal Québec,因此您的结果可能会有所不同。最好将change at any moment明确指定为参数。
以continent/region
的格式指定desired/expected time zone,例如proper time zone name,America/Montreal
或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。
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.
或者更好的是,使用预定义的Africa/Casablanca
枚举对象,每年的每个月使用一个。提示:在整个代码库中使用这些Month
对象,而不是仅使用整数,可以使您的代码更具自文档性,确保有效值并提供Month
。
LocalDate ld = LocalDate.of( 2018 , Month.SEPTEMBER , 3 ) ;
TemporalAdjuster
要移至上一个星期一,或者如果已经是星期一,则保留该日期,请使用TemporalAdjuster
类中提供的TemporalAdjusters
实现。用DayOfWeek
枚举指定所需的星期几。
LocalDate monday = ld.with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) ) ;
IsoFields
java.time 类在数周内的支持有限。将type-safety类及其常数WEEK_OF_WEEK_BASED_YEAR
和WEEK_BASED_YEAR
一起使用。
LocalDate mondayOfWeekTen = monday.with( IsoFields.WEEK_OF_WEEK_BASED_YEAR , 10 ) ;
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
weekLaterOutput:2018-W11-1
针对Java的提示(非Android):如果需要花费数周时间进行大量工作,请考虑添加IsoFields
库以访问其YearWeek
类。
ThreeTen-Extra框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧java.time日期时间类,例如legacy,java.util.Date
和Calendar
。
目前位于SimpleDateFormat
的Joda-Time项目建议迁移到maintenance mode类。
要了解更多信息,请参见java.time。并在Stack Overflow中搜索许多示例和说明。规格为Oracle Tutorial。
您可以直接与数据库交换 java.time 对象。使用符合JSR 310或更高版本的JDBC driver。不需要字符串,不需要java.sql.*
类。
在哪里获取java.time类?