5

手头的主题是在 Oracle 的名为 JD Edwards 的 ERP 软件中处理日期的特定领域的混乱问题。它的详细信息记录在这个问题中。

在编写用于处理来自 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))

4

2 回答 2

8

JD Edwards 日期定义

实际上,根据Oracle.com 页面上的这个简单描述, JD Edwards约会的细节并没有那么血腥:

关于儒略日期格式

JD Edwards World 文件中的日期字段以 Julian 格式存储。…</p>

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但那些是针对儒略日期的重新定义版本,我们计算从一个纪元(1970-01-01(ISO)而不是历史性的公元前 4714 年 11 月 24 日(公历))开始的天数,同时忽略年份共。因此,这与 JD Edwards 的定义无关,这与问题中链接的该页面上的一些不正确建议相反。

序数日期

此 JD Edwards 日期是序号日期的一个版本。序数日期有时被随意(并且错误地)称为“朱利安”日期,只是因为它共享计算天数序列的想法。但是一个序数日期计算从年初到年底的天数,这个数字总是在 1 到 365/366(闰年)之间,从某个纪元开始计算,并增长为数千个数字。

回到问题,在 java.time 中处理 JD Edwards 日期……</p>

不,我没有发现任何直接或间接的支持 JD Edwards 日期内置于 java.time。

java.date.format包似乎不知道日期的世纪,只有年份和时代。所以我无法找到定义CJD Edwards 日期的一部分。

JD Edwards 日期的最后一部分,即一年中的序数天数,在日期时间类和格式化类中都得到了很好的处理。

LocalDate

由于 JD Edwards 日期显然与 java.time 使用的 ISO 年表具有相同的逻辑,因此目前唯一真正的问题是根据这种特定格式解析和生成 String 对象。所有其他行为都可以从LocalDate.

由于我找不到java.time.format.DateTimeFormatter为此目的定义 a 的方法,我建议编写一个实用程序类来处理这些杂务。

理想情况下,我们会扩展LocalDate类,覆盖它的parsetoString方法。也许还有一种getCenturyOffset方法。但是LocalDate该类已标记final并且不能扩展。所以我会创建如下所示的此类,包装一个LocalDate.

警告:使用风险自负。新鲜的代码,几乎没有运行,几乎没有经过测试。仅作为示例,不用于生产。根据ISC 许可条款使用。

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课程已经涵盖了您。ALocalTime可以被实例化并读取为:

纳秒分辨率意味着最多位小数。处理你提到的六位数没有问题。只需做数学运算,乘/除以1_000L. LocalTime请注意,这意味着可能的数据丢失,因为如果该值来自 JD Edwards 数据之外,您可能会截断小数的最后三位(小数的第 7、8、9 位) 。[仅供参考,旧的 java.util.Date/.Calendar 类以及 Joda-Time 仅限于毫秒分辨率,用于小数的三位数字。]

不推荐:你可以做某种组合类,由 aLocalDate和 a组成LocalTime。或使用LocalDateTime. 关键问题是时区。如果 JD Edwards 日期时间始终位于某个时区(例如 UTC),那么组合和使用OffsetDateTime. 但是,如果它没有特定的时区上下文,如果这些值只是日期时间的模糊概念,而不是时间线上的特定点,那么使用LocalDateTime它没有时区。如果 JDE 始终采用 UTC,请使用OffsetDateTimeset to ZoneOffset.UTC。如果要指定时区(偏移量加上处理异常的规则,例如DST),请使用ZonedDateTime.

推荐:单独使用 LocalTime。我认为您不想在业务逻辑中使用我的 JDEdwardsLocalDate 类,尤其是因为它不是适合 java.time 框架的完整实现。我的意图是使用该类LocalDate在遇到 JDE 日期时立即转换为。JDE 时间也是如此,LocalTime立即转换为。如果他们的上下文始终是 UTC,OffsetDateTime请使用 UTC 创建一个,然后将其传递给您的业务逻辑。仅在必要时返回 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 );

时区在这里至关重要。您必须大致了解这些概念。清楚这一点LocalDateLocalTime并且LocalDateTime不是时间轴上的时刻在您将它们调整为时区(或至少与 UTC 偏移)之前,它们没有特定含义

如果不熟悉 java.time 类型,我在此答案中包含的日期时间类型图可能会对您有所帮助。

您必须了解 JDE 日期和时间的含义以及它们在您的应用程序/数据库中的使用。由于我找不到有关 JDE 时间的任何信息,因此我无法了解有关 JD Edwards 对时区的意图的任何信息。所以我不能提出更具体的建议。

于 2016-05-10T06:46:17.330 回答
2

否:Joda Time 和 Java 8 都不支持 JD Edwards 时间表示。

于 2016-05-10T05:07:21.407 回答