问题描述
我正在尝试根据一些示例城市及其各自的时区创建一个时间转换器。我正在检索 UTC 中的当前时间,然后根据每个时区的 UTC 偏移量添加或减去,然后添加或减去 12 以将时间转换为其相应的 12 小时格式,因此它可以是 am 或下午。然后,当用户从微调器中选择一个城市时,此信息会显示在 TimePicker 上。
现在的问题是我得到了正确的小时,但对于某些时区,am\pm 是倒退的。例如,我的本地时间是 EST,我想转换为 PST。假设现在是晚上 7 点,我想知道洛杉矶的时间。显示的是凌晨 4 点,而不是下午 4 点。
所以我在“纠正”时间方面遇到了麻烦。我使用了 .HOUR_OF_DAY ,我认为它应该考虑夏令时,我尝试使用 HOUR 但这并不能解决问题,只会将时间设置回一小时。 需要使用 12 小时的校正数学将 24 小时格式转换为 12 小时,但这并不像我预期的那样工作,因为正如我所提到的,虽然它确实设置为正确的小时,但它没有考虑根据实际时间选择正确的上午/下午。 此外,.setIs24HourView 设置为 false。
public int convertTime(String city)
{
//Result of taking in the UTC time and adding/subtracting the offset
int offset = 0;
//gets the calender instance of time with GMT standard,then getting hour of day
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
int UTC = c.get(Calendar.HOUR_OF_DAY);
//set offset according to city
switch(city)
{
case "New York":
offset = UTC-4;
break;
case "London":
offset = UTC+1;
break;
case "Los Angeles":
offset = UTC-7;
break;
case "dubai":
offset= UTC+4;
break;
case "Paris":
offset = UTC+2;
break;
case "Moscow":
offset = UTC+3;
break;
case "Cairo":
offset = UTC+2;
break;
case "Hong Kong":
offset = UTC+8;
break;
case "Beijing":
offset = UTC+8;
break;
case "New Delhi":
offset= UTC+5;
break;
case "Mexico City":
offset = UTC-5;
break;
case "Brasilia":
offset = UTC-3;
break;
}
//if the offset is in the AM
if(offset < 12)
{
//set am
offset = offset+12;
}
//if the offset is in the PM
else if(offset > 12)
{
//set pm
offset = offset-12;
}
else
//its twelve o'clock
offset = 12;
return offset;
}
这是它在应用程序中的显示方式,用于可视化: Time Converter
编辑:抱歉,我也应该添加这个。所以偏移量返回了“转换因子”,我在旋转器的 onItemSelected 事件中使用了它。因此,当用户从微调器中选择一个项目时,此函数会读取条目,并根据偏移值设置时间(即小时和分钟也是如此,但这是静态设置的,因为它始终会返回正确的分钟) :
@Override
public void onItemSelected(AdapterView<?> parent,View view,int position,long id)
{
if(convertSpinner.getSelectedItemPosition() == 0)
{
//display current/local time
int hour = c.get(Calendar.HOUR_OF_DAY);
convertTime.setHour(hour);
//currentTime.setHour(hour);
}
else if(convertSpinner.getSelectedItemPosition()== 1)
convertTime.setHour(conversionFactory("New York"));
else if(convertSpinner.getSelectedItemPosition()== 2)
convertTime.setHour(conversionFactory("London"));
else if(convertSpinner.getSelectedItemPosition()== 3)
convertTime.setHour(conversionFactory("Los Angeles"));
//... same processs for the other cities,shortened for obvIoUs reasons
else
convertTime.setHour(12);
//set the minute
int minute = c.get(Calendar.MINUTE);
convertTime.setMinute(minute);
}
还有我的主要内容:
private TimePicker currentTime,convertTime;
private Spinner convertSpinner;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,@Nullable ViewGroup container,@Nullable Bundle savedInstanceState)
{
View v= inflater.inflate(R.layout.fragment_time,container,false);
convertTime = v.findViewById(R.id.convert_clock);
convertSpinner = v.findViewById(R.id.convert_spinner);
convertTime.setIs24HourView(false);
convertTime.setClickable(false);
//for convert spinner
ArrayAdapter<CharSequence> adapter2 = ArrayAdapter.createFromresource(getActivity(),R.array.time_cities,android.R.layout.simple_spinner_item);
adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
convertSpinner.setAdapter(adapter2);
convertSpinner.setonItemSelectedListener(this);
return v;
}
解决方法
您无需重新发明轮子。
与其放置如此多容易出错的复杂逻辑,不如使用 OOTB(即开即用)日期时间 API。无论是现代 API 还是旧版 API,都有更简单的方法来完成您尝试使用复杂自定义逻辑执行的操作。
但是,旧的日期时间 API,即来自 java.util
的 API 及其格式 API SimpleDateFormat
已经过时且容易出错。建议完全停止使用它们并切换到 modern date-time API。出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7。如果您正在工作对于 Android 项目并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring 和 How to use ThreeTenABP in Android Project。
定义时区不仅需要城市,还需要大陆。
表示时区字符串的标准格式是 Continent/City。
使用现代 API:
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class Main {
public static void main(String... args) {
// Test
System.out.println(getOffset("Europe/London"));
System.out.println(getOffset("Africa/Johannesburg"));
System.out.println(getOffset("America/Chicago"));
System.out.println(getOffset("Asia/Calcutta"));
}
public static ZoneOffset getOffset(String zoneId) {
return ZonedDateTime.now(ZoneId.of(zoneId)).getOffset();
}
}
输出:
Z
+02:00
-06:00
+05:30
请注意,Z
代表 Zulu
时间,它指定 UTC
(时区偏移为 +00:00
小时)。
使用旧 API:
import java.util.TimeZone;
public class Main {
public static void main(String... args) {
// Test
System.out.println(getOffset("Europe/London"));
System.out.println(getOffset("Africa/Johannesburg"));
System.out.println(getOffset("America/Chicago"));
System.out.println(getOffset("Asia/Calcutta"));
}
public static String getOffset(String zoneId) {
int offset = TimeZone.getTimeZone(zoneId).getRawOffset();// milliseconds to be added to UTC
int seconds = offset / 1000;
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
return "GMT" + (offset >= 0 ? "+" : "-") + String.format("%02d",Math.abs(hours)) + ":"
+ String.format("%02d",minutes);
}
}
输出:
GMT+00:00
GMT+02:00
GMT-06:00
GMT+05:30
如何将日期时间从一个时区转换为另一个时区:
使用现代日期时间 API,您可以使用 ZonedDateTime#withZoneSameInstant
和 OffsetDateTime#withOffsetSameInstant
分别获取指定时区中的 ZonedDateTime
和 OffsetDateTime
。
ZonedDateTime 演示
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Main {
public static void main(String... args) {
// Test
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.systemDefault());
System.out.println(zdt);
System.out.println(getZonedDateTimeWithZoneId(zdt,"Europe/London"));
System.out.println(getZonedDateTimeWithZoneId(zdt,"Africa/Johannesburg"));
System.out.println(getZonedDateTimeWithZoneId(zdt,"America/Chicago"));
System.out.println(getZonedDateTimeWithZoneId(zdt,"Asia/Calcutta"));
}
public static ZonedDateTime getZonedDateTimeWithZoneId(ZonedDateTime zdt,String zoneId) {
return zdt.withZoneSameInstant(ZoneId.of(zoneId));
}
}
输出:
2020-12-28T20:03:54.093476Z[Europe/London]
2020-12-28T20:03:54.093476Z[Europe/London]
2020-12-28T22:03:54.093476+02:00[Africa/Johannesburg]
2020-12-28T14:03:54.093476-06:00[America/Chicago]
2020-12-29T01:33:54.093476+05:30[Asia/Calcutta]
OffsetDateTime 演示
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class Main {
public static void main(String... args) {
// Test
OffsetDateTime odt = OffsetDateTime.now(ZoneId.systemDefault());
System.out.println(odt);
System.out.println(getOffsetDateTimeWithZoneId(odt,"Europe/London"));
System.out.println(getOffsetDateTimeWithZoneId(odt,"Africa/Johannesburg"));
System.out.println(getOffsetDateTimeWithZoneId(odt,"America/Chicago"));
System.out.println(getOffsetDateTimeWithZoneId(odt,"Asia/Calcutta"));
}
public static OffsetDateTime getOffsetDateTimeWithZoneId(OffsetDateTime odt,String zoneId) {
return odt.withOffsetSameInstant(getOffset(zoneId));
}
public static ZoneOffset getOffset(String zoneId) {
return ZonedDateTime.now(ZoneId.of(zoneId)).getOffset();
}
}
输出:
2020-12-28T20:08:25.026349Z
2020-12-28T20:08:25.026349Z
2020-12-28T22:08:25.026349+02:00
2020-12-28T14:08:25.026349-06:00
2020-12-29T01:38:25.026349+05:30
使用旧 API:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class Main {
public static void main(String... args) throws ParseException {
// Test
Date date = Calendar.getInstance().getTime();
System.out.println(date);
System.out.println(getFormattedDateTimeWithZoneId(date,"Europe/London"));
System.out.println(getFormattedTimeWithZoneId(date,"Europe/London"));
System.out.println(getFormattedDateTimeWithZoneId(date,"Africa/Johannesburg"));
System.out.println(getFormattedTimeWithZoneId(date,"Africa/Johannesburg"));
System.out.println(getFormattedDateTimeWithZoneId(date,"America/Chicago"));
System.out.println(getFormattedTimeWithZoneId(date,"America/Chicago"));
System.out.println(getFormattedDateTimeWithZoneId(date,"Asia/Calcutta"));
System.out.println(getFormattedTimeWithZoneId(date,"Asia/Calcutta"));
}
public static String getFormattedDateTimeWithZoneId(Date date,String zoneId) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss a zzz",Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone(getOffset(zoneId)));
return sdf.format(date);
}
public static String getFormattedTimeWithZoneId(Date date,String zoneId) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss a",Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone(getOffset(zoneId)));
return sdf.format(date);
}
public static String getOffset(String zoneId) {
int offset = TimeZone.getTimeZone(zoneId).getRawOffset();// milliseconds to be added to UTC
int seconds = offset / 1000;
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
return "GMT" + (offset >= 0 ? "+" : "-") + String.format("%02d",minutes);
}
}
输出:
Mon Dec 28 20:47:25 GMT 2020
2020-12-28 08:47:25 PM GMT+00:00
08:47:25 PM
2020-12-28 10:47:25 PM GMT+02:00
10:47:25 PM
2020-12-28 02:47:25 PM GMT-06:00
02:47:25 PM
2020-12-29 02:17:25 AM GMT+05:30
02:17:25 AM
从 Trail: Date Time 了解现代日期时间 API。
,java.time
考虑使用 java.time(现代 Java 日期和时间 API)来处理您的时间工作。尤其是当它像您一样重要时。
获取洛杉矶的当前时间:
LocalTime timeInLa = LocalTime.now(ZoneId.of("America/Los_Angeles"));
System.out.println("Current time in LA: " + timeInLa);
我刚才运行时的输出:
洛杉矶当前时间:11:10:18.975
对于其他城市,请提供相应的时区 ID,例如 Europe/London
或 Asia/Dubai
。巴西利亚的 ID 是 America/Sao_Paulo
。我建议您构建一个从城市名称到时区 ID 的地图,这样您就可以通过地图查找来获取 ID,并且不需要长的 switch
语句。
一旦我们有了 LocalTime
对象,我们就可以取出一天中的小时。
int hourOfDayInLa = timeInLa.getHour();
System.out.println("Hour of day in LA: " + hourOfDayInLa);
洛杉矶一天中的几点:11
您是否需要对此值进行任何调整,以及如何让您的时间选择器显示正确的时间 - 我当然不希望如此,但这不是我知道也不能告诉您的事情。
我可以告诉您的是,我们还可以从 LocalTime
对象中获取 AM 或 PM 的时钟小时数,以及 0 表示 AM 或 1 表示 PM:
int clockHourOfAmOrPm = timeInLa.get(ChronoField.CLOCK_HOUR_OF_AMPM);
int amOrPmIndexInLa = timeInLa.get(ChronoField.AMPM_OF_DAY);
String amPm = amOrPmIndexInLa == 0 ? "AM" : "PM";
System.out.format("Clock hour %d (1 through 12) %s (code %d)%n",clockHourOfAmOrPm,amPm,amOrPmIndexInLa);
时钟 11 点(1 到 12 点)AM(代码 0)
(如果最终目标是字符串 AM
或 PM
,我们应该为此使用格式化程序:我在想,如果您想根据是 AM 还是 PM 进行进一步处理,int
值为 0 或 1 会更实用。)
问题:java.time 不需要 Android API 级别 26 吗?
java.time 在较旧和较新的 Android 设备上都能很好地工作。它只需要至少 Java 6。
- 在 Java 8 及更高版本以及更新的 Android 设备(从 API 级别 26 开始)中,内置了现代 API。
- 在非 Android Java 6 和 7 中获得 ThreeTen Backport,现代类的向后移植(ThreeTen for JSR 310;请参阅底部的链接)。
- 在较旧的 Android 上,使用脱糖或 ThreeTen Backport 的 Android 版本。它被称为 ThreeTenABP。在后一种情况下,请确保使用子包从
org.threeten.bp
导入日期和时间类。
链接
- Oracle tutorial: Date Time 解释如何使用 java.time。
-
Java Specification Request (JSR) 310,其中首次描述了
java.time
。 -
ThreeTen Backport project,将
java.time
向后移植到 Java 6 和 7(JSR-310 的 ThreeTen)。 - Java 8+ APIs available through desugaring
- ThreeTenABP,ThreeTen Backport 的 Android 版
- Question: How to use ThreeTenABP in Android Project,非常详尽的解释。