手头的主题是一个混乱的特定于域的问题,在Oracle的ERP软件JD Edwards中使用日期。其详细信息记录在this question。
中在编写用于处理JD Edwards的日期和时间的包装类之前,我想知道JodaTime或Java 8是否为这种独特的时间格式引入了任何特殊支持,或者我是否必须进行重要的字符串操作而不管我使用的库。
这是一个模糊的问题,所以请只有在您对此问题有特定了解和/或JodaTime / Java 8 / JSR 310时才能回复。
此外: 按照Basil Bourque的要求,添加伴随上述日期的时间戳示例。以下是来自不同表的日期/时间字段的两个示例:
JCSBMDATE:115100,JCSBMTIME:120102.0
RLUPMJ:114317,RLUPMT:141805.0
此外,日期变量被转换为BigDecimal,时间是Double。因此,我可能会保留字符串解析器,但也会编写本地采用BigDecimal / Double值的工厂方法。
似乎时间字段实际上是从一天开始的毫秒数(不是秒数),以及" .0"可以忽略。因此,必须执行转换和计算,如下所示:
localDate.atTime(LocalTime.ofNanoOfDay(Long.parseLong(jdeTime)* 1000000))
答案 0 :(得分:8)
根据对JD Edwards的简单描述,实际上a page at Oracle.com日期的详细信息并不是那么血腥:
关于朱利安日期格式
JD Edwards World文件中的日期字段以Julian格式存储。 ...
Julian(* JUL)日期格式为CYYDDD,其中:
C加到19创造世纪,即0 + 19 = 19,1 + 19 = 20.YY是本世纪内的一年,DDD是一年中的一天。
条款:
C
部分称为“世纪偏移”,要添加到19
多少个世纪。 0
年使用19xx
,1
年使用20xx
。DDD
称为“DayOfYear”,“序数日期”是另一个术语。使用“朱利安”作为一年中的一天数是常见但不正确,与Julian Day冲突。java.time框架不包括直接支持解析或生成此格式的字符串,而不是我能找到的。
JulianFields
有java.time.temporal.JulianFields
,但这些是Julian dates的重新定义版本,我们计算一个时代的天数(1970-01-01(ISO)而不是历史的11月24日,公元前4714年(保育格列高利)),完全忽略了几年。因此,这与JD Edwards定义无关,与该问题中链接的该页面上的一些错误建议相反。
此JD Edwards日期是ordinal date的版本。序数日期有时候被称为“julian"”" julian"约会只是因为它分享了计算一系列天数的想法。但是,序数日期是从年初到年末的天数,总数在1到365/366(闰年)之间,不计算自某个时代以来的数量,并且数量增加到数千个。
回到问题,在java.time中处理JD Edwards日期......
不,我没有找到任何直接或间接支持JD.time中内置的JD Edwards日期。
java.date.format包似乎没有意识到约会的世纪,只有年份和时代。所以我无法找到定义JD Edwards日期的C
部分。
JD Edwards日期的最后一部分,即一年中的序数天数,在日期时间类和格式化类中都得到很好的处理。
LocalDate
由于JD Edwards日期显然与java.time使用的ISO年表具有相同的逻辑,因此唯一真正的问题是根据此特定格式解析和生成String对象。可以从LocalDate
。
由于我找不到为此目的定义java.time.format.DateTimeFormatter
的方法,我建议编写一个实用程序类来处理这些杂务。
理想情况下,我们会扩展LocalDate
类,覆盖其parse
和toString
方法。也许是getCenturyOffset
方法。但LocalDate
类标记为final
,无法扩展。所以我会创建如下所示的类,包装LocalDate
。
CAVEAT:使用风险由您自行承担。新鲜的代码,几乎没有运行,几乎没有测试。仅作为一个例子,不用于生产。根据{{3}}的条款使用。
package com.example.whatever;
import java.time.LocalDate;
import java.time.ZoneId;
/**
* Wraps a 'LocalDate' to provide parsing/generating of strings in format known
* as JD Edwards date.
*
* Format is CYYDDD where C is the number of centuries from 1900, YY is the year
* within that century, and DDD is the ordinal day within the year (1-365 or
* 1-366 in Leap Year).
*
* Immutable object. Thread-safe (hopefully! No guarantees).
*
* I would rather have done this by extending the 'java.time.LocalDate' class, but that class is marked 'final'.
*
* Examples: '000001' is January 1 of 1900. '116032' is February 1, 2016.
*
* © 2016 Basil Bourque. This source code may be used according to terms of the ISC License at https://opensource.org/licenses/ISC
*
* @author Basil Bourque
*/
public class JDEdwardsLocalDate {
private LocalDate localDate = null;
private int centuryOffset;
private int yearOfCentury;
private String formatted = null;
// Static Factory method, in lieu of public constructor.
static public JDEdwardsLocalDate from ( LocalDate localDateArg ) {
return new JDEdwardsLocalDate ( localDateArg );
}
// Static Factory method, in lieu of public constructor.
static public JDEdwardsLocalDate parse ( CharSequence charSequenceArg ) {
if ( null == charSequenceArg ) {
throw new IllegalArgumentException ( "Passed CharSequence that is null. Message # 0072f897-b05f-4a0e-88d9-57cfd63a712c." );
}
if ( charSequenceArg.length () != 6 ) {
throw new IllegalArgumentException ( "Passed CharSequence that is not six characters in length. Message # eee1e134-8ec9-4c92-aff3-9296eac1a84a." );
}
String string = charSequenceArg.toString ();
// Should have all digits. Test by converting to an int.
try {
int testAsInteger = Integer.parseInt ( string );
} catch ( NumberFormatException e ) {
throw new IllegalArgumentException ( "Passed CharSequence contains non-digits. Fails to convert to an integer value. Message # 0461f0ee-b6d6-451c-8304-6ceface05332." );
}
// Validity test passed.
// Parse.
int centuryOffset = Integer.parseInt ( string.substring ( 0 , 1 ) ); // Plus/Minus from '19' (as in '1900').
int yearOfCentury = Integer.parseInt ( string.substring ( 1 , 3 ) );
int ordinalDayOfYear = Integer.parseInt ( string.substring ( 3 ) );
int centuryStart = ( ( centuryOffset + 19 ) * 100 ); // 0 -> 1900. 1 -> 2000. 2 -> 2100.
int year = ( centuryStart + yearOfCentury );
LocalDate localDate = LocalDate.ofYearDay ( year , ordinalDayOfYear );
return new JDEdwardsLocalDate ( localDate );
}
// Constructor.
private JDEdwardsLocalDate ( LocalDate localDateArg ) {
this.localDate = localDateArg;
// Calculate century offset, how many centuries plus/minus from 1900.
int year = this.localDate.getYear ();
int century = ( year / 100 );
this.yearOfCentury = ( year - ( century * 100 ) ); // example: if 2016, return 16.
this.centuryOffset = ( century - 19 );
// Format as string.
String paddedYearOfCentury = String.format ( "%02d" , this.yearOfCentury );
String paddedDayOfYear = String.format ( "%03d" , this.localDate.getDayOfYear () );
this.formatted = ( this.centuryOffset + paddedYearOfCentury + paddedDayOfYear );
}
@Override
public String toString () {
return this.formatted;
}
public LocalDate toLocalDate () {
// Returns a java.time.LocalDate which shares the same ISO chronology as a JD Edwards Date.
return this.localDate;
}
public int getDayOfYear () {
// Returns ordinal day number within the year, 1-365 inclusive or 1-366 for Leap Year.
return this.localDate.getDayOfYear();
}
public int getYear () {
// Returns a year number such as 2016.
return this.localDate.getYear();
}
public int getYearOfCentury () {
// Returns a number within 0 and 99 inclusive.
return this.yearOfCentury;
}
public int getCenturyOffset () {
// Returns 0 for 19xx dates, 1 for 20xx dates, 2 for 21xx dates, and so on.
return this.centuryOffset;
}
public static void main ( String[] args ) {
// '000001' is January 1, 1900.
JDEdwardsLocalDate jde1 = JDEdwardsLocalDate.parse ( "000001" );
System.out.println ( "'000001' = JDEdwardsLocalDate: " + jde1 + " = LocalDate: " + jde1.toLocalDate () + " Should be: January 1, 1900. " );
// '116032' is February 1, 2016.
JDEdwardsLocalDate jde2 = JDEdwardsLocalDate.parse ( "116032" );
System.out.println ( "'116032' = JDEdwardsLocalDate: " + jde2 + " = LocalDate: " + jde2.toLocalDate () + " Should be: February 1, 2016." );
// Today
LocalDate today = LocalDate.now ( ZoneId.systemDefault () );
JDEdwardsLocalDate jdeToday = JDEdwardsLocalDate.from ( today );
System.out.println ( "LocalDate.now(): " + today + " = JDEdwardsLocalDate: " + jdeToday + " to LocalDate: " + jdeToday.toLocalDate () );
}
}
跑步时。
' 000001' = JDEdwardsLocalDate:000001 = LocalDate:1900-01-01应该是:1900年1月1日。
' 116032' = JDEdwardsLocalDate:116032 = LocalDate:2016-02-01应该是:2016年2月1日。
LocalDate.now():2016-05-09 = JDEdwardsLocalDate:116130 to LocalDate:2016-05-09
至于JD Edwards时间格式,我搜索过,找不到任何文档。如果您了解某些内容,请编辑您的问题以添加链接。 JDE时代唯一提到的似乎是从午夜开始的秒数。
如果是这种情况(从午夜开始计算),ISC License课程就可以了。 LocalTime
可以实例化并读作:
java.time.LocalTime
,withSecond
)ofSecondOfDay
,withNano
)纳秒分辨率意味着小数部分的 9 位。处理您提到的六位数没问题。只需做数学运算,乘以/除1_000L
。请注意,这意味着可能的数据丢失,因为如果LocalTime
值来自JD Edwards数据之外,您可能会截断分数的最后三位数(小数部分的第7位,第8位,第9位)。 [仅供参考,旧的java.util.Date/.Calendar类以及Joda-Time限制为毫秒分辨率,小数部分的三位数。]
不推荐:可以执行某种由LocalDate
和LocalTime
组成的组合类。或者使用LocalDateTime
。关键问题是时区。如果JD Edwards日期时间始终在某个时区(如UTC),那么组合并使用OffsetDateTime
可能是有意义的。但是,如果它没有特定的时区上下文,如果值只是日期时间的模糊概念而不是时间轴上的特定点,则使用LocalDateTime
,因为它没有时区。如果JDE始终为UTC,请将OffsetDateTime
设置为ZoneOffset.UTC
。如果要指定时区(偏移加规则以处理ofNanoOfDay
等异常),请使用ZonedDateTime
。
推荐:分别使用LocalTime。我不认为你想在业务逻辑中使用我的JDEdwardsLocalDate类,特别是因为它不是适合java.time框架的完整实现。 我的目的是在遇到JDE日期时使用该类立即将转换为LocalDate
。对于JDE时间也是如此,立即转换为DST。如果它们的上下文始终是UTC,请使用UTC创建LocalTime
,然后将其传递给业务逻辑。只回到JDE日期&必要时(持久化到该JDE类型的数据库列,或向用户报告期望JDE演示)。
OffsetDateTime odt = OffsetDateTime.of( myLocalDate , myLocalTime , ZoneOffset.UTC );
如果JDE日期&时间隐含了一些其他背景,然后分配预期的时区。
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.of( myLocalDate , myLocalTime , zoneId );
时区在这里至关重要。您必须了解一般的概念。请注意,时间表上的LocalDate
和LocalTime
以及LocalDateTime
不 。 它们没有特定含义,直到您将它们调整为时区(或至少OffsetDateTime
)。
如果不熟悉java.time类型,offset-from-UTC中包含的日期时间类型图可能对您有帮助。
你必须明白JDE日期和时间的含义。时间及其在您的应用/数据库中的使用。由于我无法找到关于JDE时间的任何信息,我无法了解JD Edwards对时区的意图。所以我不能提出更具体的建议。
答案 1 :(得分:2)
否:Joda Time和Java 8都不支持JD Edwards时间表示。