oracle - JodaTime或Java 8是否具有对JD Edwards日期和时间的特殊支持?

标签 oracle java-8 jodatime jdedwards

当前的主题是一个混乱的特定于域的问题,该问题在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))

最佳答案

JD Edwards日期已定义
实际上,根据JD Edwards上的简单描述,a page at Oracle.com日期的细节并不是那么复杂:

关于儒略日期格式
JD Edwards World文件中的日期字段以Julian格式存储。 …
朱利安(* JUL)日期格式为CYYDDD,其中:
C被加到19以创建世纪,即0 + 19 = 19,1 + 19 =20。YY是世纪内的年份,DDD是年份中的一天。

条款:

我将C部分称为“世纪偏移”,即要添加到19多少个世纪。 0年使用19xx1年使用20xx
java.time框架将DDD称为“ DayOfYear”,而“原始日期”是另一个术语。使用“ Julian”表示一年内的天数是常见的,但不正确,这与Julian Day冲突。

java.time框架不直接支持解析或生成这种格式的字符串,我找不到。
JulianFields
java.time.temporal.JulianFields,但是这些是针对Julian dates的重新定义版本的,其中我们计算从一个纪元(1970-01-01(ISO),而不是历史性的公元前4714年11月24日(多义的格里高里))开始的天数,却完全忽略了岁月。因此,这与JD Edwards定义无关,这与“问题”中链接的该页面上的一些错误建议相反。
序数日期
此JD Edwards日期是ordinal date的版本。有时将序数日期偶然地(并且错误地)称为“居里”日期,是因为它具有计数天数序列的想法。但是,按序排列的日期是指从年初到年底的天数,通常为1到365/366(le年)之间的数字,自某个时期以来就不算在内,并增长到成千上万个数字。
返回问题,在java.time中处理JD Edwards日期…
不,我没有发现直接或间接支持java.time内置的JD Edwards日期。
java.date.format软件包似乎没有意识到日期的世纪,只有年份和时代。因此,我找不到定义JD Edwards日期的C部分的方法。
JD Edwards日期的最后一部分,即一年中的天数,在日期时间类和格式化类中均得到很好的处理。
包装LocalDate
由于JD Edwards日期显然具有与java.time使用的ISO年表相同的逻辑,因此眼前唯一真正的问题是根据此特定格式解析和生成String对象。其他所有行为都可以通过LocalDate利用。
由于我找不到为此目的定义java.time.format.DateTimeFormatter的方法,因此建议编写一个实用程序类来处理这些琐事。
理想情况下,我们将扩展LocalDate类,覆盖其parsetoString方法。也许是getCenturyOffset方法。但是LocalDate类被标记为final,并且不能扩展。因此,我将创建如下所示的此类,并包装一个LocalDate
注意:使用风险自负。新鲜的代码,几乎没有运行,几乎没有测试。仅作为示例,不用于生产。根据ISC License的条款使用。

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到LocalDate:2016-05-09

JD Edwards的时间
至于JD Edwards的每日时间格式,我进行了搜索,但找不到任何文档。如果您知道某些内容,请编辑问题以添加链接。唯一提到的JDE时间似乎是从午夜算起的秒数。
如果是这种情况(从午夜开始算起),您就可以覆盖java.time.LocalTime类。可以实例化LocalTime并读取为:

从一天开始算起整秒(withSecondofSecondOfDay
从一天开始算起的小数秒,以纳秒为单位(withNanoofNanoOfDay

纳秒分辨率表示最多十进制的九位数。处理您提到的六位数没有问题。只需做一下数学运算,即可将1_000L乘以/除以。请注意,这意味着可能会丢失数据,因为如果LocalTime值来自JD Edwards数据之外,则您可能会截断小数点的最后三位数字(十进制小数点的第7、8、9位)。 [FYI,旧的java.util.Date/.Calendar类以及Joda-Time的毫秒分辨率仅限于三位数的小数。
不推荐:您可以执行某种组合类,由LocalDateLocalTime组成。或使用LocalDateTime。关键问题是时区。如果JD Edwards的日期时间始终处于某个时区(例如UTC),则可以合并并使用OffsetDateTime。但是,如果没有特定的时区上下文,则值只是日期时间的模糊概念,而不是时间轴上的特定点,请使用LocalDateTime,因为它没有时区。如果JDE始终采用UTC,则将OffsetDateTime设置为ZoneOffset.UTC。如果要指定时区(偏移量以及用于处理异常的规则,例如DST),请使用ZonedDateTime
推荐:单独使用LocalTime。我不希望您在业务逻辑中使用我的JDEdwardsLocalDate类,尤其是因为它不是适合java.time框架的完整实现。我的意图是在遇到JDE日期时使用该类立即将其转换为LocalDate。对于JDE,该时间相同,请立即转换为LocalTime。如果他们的上下文始终是UTC,请使用UTC创建OffsetDateTime,然后将其传递到您的业务逻辑中。仅在必要时返回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 );

时区在这里至关重要。您必须大致了解这些概念。请清楚,LocalDateLocalTimeLocalDateTime不在时间轴上。在您将它们调整为时区(或至少为offset-from-UTC)之前,它们没有特定的含义。
如果不熟悉java.time类型,我在this Answer上包含的日期时间类型图可能会为您提供帮助。
并且您必须了解JDE日期和时间的含义及其在您的应用程序/数据库中的用法。因为我找不到有关JDE时间的任何信息,所以也无法了解有关JD Edwards针对时区的意图的任何信息。因此,我无法提出更具体的建议。

关于oracle - JodaTime或Java 8是否具有对JD Edwards日期和时间的特殊支持?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37127460/

相关文章:

java - 在 JDO/DataNucleus 中,我可以查询 Joda-Time DateTime 对象吗?

performance - 有没有办法修复共享池中的 Oracle 查询

Java 8 的流 : why parallel stream is slower?

java - JodaTime 从 Java.util.Date 转换为 DateTime(或 LocalDate)

java - 基于注释参数创建切入点

java - 将 Instant 格式化为 String 时出现 UnsupportedTemporalTypeException

java - 在java中将多个位置的本地日期时间转换为UTC

sql - 如何使用空字符串具有空值的主键?

ruby-on-rails - 将Rails与Oracle一起使用时,为什么会出现“无监听器”错误?

Oracle APEX Interactive Report 条件列链接显示