我想解析字符串datetime&阿拉伯-印度数字时区,所以我写了一个这样的代码:
String dateTime = "٢٠٢١-١١-٠٨T٠٢:٢١:٠٨+٠٢:٠٠";
char zeroDigit = '٠';
Locale locale = Locale.forLanguageTag("ar");
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX")
.withLocale(locale)
.withDecimalStyle(DecimalStyle.of(locale).withZeroDigit(zeroDigit));
ZonedDateTime parsedDateTime = ZonedDateTime.parse(dateTime, pattern);
assert parsedDateTime != null;
但我收到了一个例外:
java.time.format.DateTimeParseException:无法在索引19 中分析文本"٢٠">
我检查了很多关于Stackoverflow的问题,但我仍然不明白我做错了什么。
当时区不使用阿拉伯-印度数字时,它可以与dateTime = "٢٠٢١-١١-٠٨T٠٢:٢١:٠٨+02:00"
配合使用。
您的dateTime
字符串是错误的,被误解了。它显然试图遵守ISO8601格式,但失败了。因为ISO 8601格式使用US-ASCII数字。
如果只有数字符合ISO8601,java.time的类(Instant
、OffsetDateTime
和ZonedDateTime
(将在没有任何格式化程序的情况下解析字符串。在绝大多数情况下,我会采取你的方式:尝试按原样解析字符串。在这种情况下不会。对我来说,在解析之前更正字符串更有意义。
String dateTime = "٢٠٢١-١١-٠٨T٠٢:٢١:٠٨+٠٢:٠٠";
char[] dateTimeChars = dateTime.toCharArray();
for (int index = 0; index < dateTimeChars.length; index++) {
if (Character.isDigit(dateTimeChars[index])) {
int digitValue = Character.getNumericValue(dateTimeChars[index]);
dateTimeChars[index] = Character.forDigit(digitValue, 10);
}
}
OffsetDateTime odt = OffsetDateTime.parse(CharBuffer.wrap(dateTimeChars));
System.out.println(odt);
输出:
2021-11-08T02:21:08+02:00
编辑:当然,如果你能教育字符串的发布者使用US-ASCII数字,那就更好了。
编辑:我知道我链接到下面的维基百科文章说:
表示必须用阿拉伯数字组合书写以及特定的计算机字符(例如"-"、":"、"T"、"W"、"Z"(在标准中被赋予特定含义的…
这是造成混乱的一个原因。链接到的文章阿拉伯数字说:
阿拉伯数字是十位数字:0、1、2、3、4、5、6、7、8和9。
编辑:如何转换每个数字:Character.getNumericValue()
从代表一个数字的char
转换为等于该数字代表的数字的int
,因此'٠'
转换为0,'٢'
转换为2,等等。它适用于所有数字字符(不仅仅是阿拉伯语和ASCII字符(。Character.forDigit()
执行某种相反的转换,只总是到US ASCII,所以0到'0'
,2到'2'
,等等。
编辑:感谢@Holger在本文中引起我对CharBuffer
的关注。CharBuffer
实现了CharSequence
,这是java.time的parse
方法所需要的类型,因此避免了我们将char
数组转换回String
。
链接
- 维基百科文章:ISO 8601
- 维基百科文章:阿拉伯数字
错误消息指出问题出现在输入字符串的索引19处。
字符19是输入字符串中的+
字符。这意味着偏移量(在模式中由XXX
表示(无法解析。
问题不在于+
本身。问题是,像+05:00
这样的时区偏移永远不会本地化。
文档中没有提到这一点,所以我不得不去DateTimeFormatterBuilder的源代码进行验证
在这个类里面是这个内部类:
static final class OffsetIdPrinterParser implements DateTimePrinterParser {
在该类中,我们可以找到一个对私有parseHour、parseMinute和parseSeconds方法进行调用的解析方法。
这些方法中的每一个都委托给一个私有的parseDigits方法。在这种方法中,我们可以看到只考虑ASCII数字:
char ch1 = parseText.charAt(pos++);
char ch2 = parseText.charAt(pos++);
if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
return false;
}
因此,这里的答案是,时区偏移量必须由ASCII数字组成,而不考虑区域设置。