我有一个数据库,它将Dates和DateTimes(分别作为INTEGER和DOUBLEs)存储为Modified Julian Day Numbers(MJD)。修改后的朱利安日数是从1858年11月17日午夜开始的连续天数。根据定义,它们总是以UTC计算,与GMT的偏移量为+0:00,并且不适用于夏令时。这些属性简化了DateTimes的某些操作,例如优先级和日期算术。
缺点是MJD必须从UTC重新定位,并在使用前后重新定位到UTC,特别是对于那些日界限非常重要的应用程序(例如,Medicare会将可计费日期边界识别为 - 本地区的午夜 - 时间)。
考虑以下静态工厂方法,其目的是将MJD(在UTC中)非本地化为“区域日期编号”(基本上,已添加适当偏移量的MJD使其表示本地DateTime):
public static MJD ofDayNumberInZone(double regDN, ZoneId zone) {
:
:
}
直观显而易见,如果您有本地日期和时间,并且您知道当地时区,那么您应该拥有所需的所有信息,以便将regDN
偏移回UTC(根据要求一个MJD)。
实际上,使用以前的Java Calendar API编写此函数非常简单。 regDN
很容易转换为Date
,用于设置GregorianCalendar实例。知道“本地时区”后,日历会报告ZONE_OFFSET和DST_OFFSET值,然后可以使用这些值将日期数调整为MJD。
这是我尝试在Java 8 DateTime API中编写类似的算法:
public static MJD ofDayNumberInZone(double zonedMJD, ZoneId zone) {
double epochSec = ((zonedMJD - MJD.POSIX_EPOCH_AS_MJD) * 86400.0);
LocalDateTime dt = LocalDateTime
.ofEpochSecond(
(long) epochSec,
(int) (epochSec - Math.floor(epochSec) * 1000000000.0),
---> zone.getRules().getOffset( <Instant> )
);
}
问题显示在箭头上。使用ofEpochSecond方法构造一个LocalDateTime实例似乎需要事先知道偏移量,这似乎违反直觉(我已经有本地时间和时区,这是我想要的偏移量)。
我没有成功找到一种简单的方法来使用Java 8 API从当地时间回到UTC获得偏移量。虽然我可以继续使用旧的Calendar API,但新的DateTime库提供了引人注目的优势......所以我想尝试解决这个问题。我错过了什么?
编辑:这是一个使用旧的Java Calendar API的示例,说明如何将任意时区中的天数和小数天数“解除区域化”为UTC。此方法采用double,即“区域化日期数”和时区对象。它使用GregorianCalendar将参数转换为Epoch的UTC计数毫秒:
private static final Object lockCal = new Object();
private static final SimpleDateFormat SDF = new SimpleDateFormat();
private static final GregorianCalendar CAL = new
GregorianCalendar(TimeZone.getTimeZone(HECTOR_ZONE));
:
:
public static MJD ofDayNumberInZone(double rdn, TimeZone tz) {
Date dat = new Date((long) ((rdn - MJD.POSIX_EPOCH_AS_MJD) *
(86400.0 * 1000.0)));
return MJD.ofDateInZone(dat, tz);
}
public static MJD ofDateInZone(Date dat, TimeZone tz) {
long utcMillisFromEpoch;
synchronized(lockCal) {
CAL.setTimeZone(tz);
CAL.setTime(dat);
utcMillisFromEpoch = CAL.getTimeInMillis();
}
return MJD.ofEpochMillisInUTC(utcMillisFromEpoch);
}
public static MJD ofEpochMillisInUTC(long millis)
{ return new MJD((millis / (86400.0 * 1000.0)) + POSIX_EPOCH_AS_MJD); }
答案 0 :(得分:1)
根据您的评论,您的核心问题似乎是将没有时区的日期时间(LocalDateTime
)转换为分区时刻(ZonedDateTime
)的模糊性。您解释夏令时(DST)等异常可能导致无效值。
ZonedDateTime zdt = myLocalDateTime.atZone( myZoneId );
这是事实。登陆DST“Spring-forward”或“Fall-back”切换时没有完美的解决方案。但是,java.time类做通过采用某种策略来解决歧义。您可能会也可能不会同意该政策。但如果你同意,那么你可以依靠java.time来确定结果。
引用ZonedDateTime.ofLocal
的文档:
在重叠的情况下,时钟被设置回来,有两个有效的偏移。如果首选偏移是有效偏移之一,则使用它。否则使用较早的有效偏移,通常对应于“夏天”。
在间隙的情况下,时钟向前跳跃,没有有效的偏移。而是将本地日期时间调整为稍后的间隙长度。对于典型的一小时夏令时更改,本地日期时间将在一小时后移动到通常对应于“夏天”的偏移量。
LocalDate modifiedJulianEpoch = LocalDate.of( 1858 , 11 , 17 );
LocalDate today = LocalDate.now( ZoneOffset.UTC );
long days = ChronoUnit.DAYS.between ( modifiedJulianEpoch , today );
今天:2017-03-19 天:57831
我不太了解你的问题。但在我看来,MJD(改良朱利安日)的目的是找到一种方法来追踪“一个真实的时间”,以避免时区的混乱。在标准的ISO 8601日历系统中,UTC比“One True Time”的角色扮演。所以我建议坚持UTC。
当您需要考虑某个地区的挂钟时间时,例如您所在地区年终的Medicare示例,请确定区域挂钟时间,然后转换为UTC。根据定义,java.time中的Instant
类始终为UTC。
ZoneId z = ZoneId.of( "America/Los_Angeles" );
LocalDate localDate = LocalDate.now( z );
ZonedDateTime firstMomentNextDay = localDate.plusDays( 1 ).atStartOfDay( z );
Instant medicareExpiration = firstMomentNextDay.toInstant(); // UTC
BigDecimal modJulDays = this.convertInstantToModifiedJulianDays( medicareExpiration ) ;
在处理精度很重要的小数位数时使用BigDecimal
。使用double
,Double
,float
或Float
意味着使用浮点技术来消除准确性,从而提高性能。
以下是对从BigDecimal(Modified Julian Days)到Instant进行转换的一些代码的粗略描述。我想一些聪明的人可能会找到这个代码的更精简或更简洁的版本,但我的代码似乎在起作用。使用风险由您自己承担。我根本没有测试过这段代码。
public Instant convertModifiedJulianDaysToInstant ( BigDecimal modJulDays ) {
Instant epoch = OffsetDateTime.of ( 1858, 11, 17, 0, 0, 0, 0, ZoneOffset.UTC ).toInstant ( ); // TODO: Make into a constant to optimize.
long days = modJulDays.toBigInteger ( ).longValue ( );
BigDecimal fractionOfADay = modJulDays.subtract ( new BigDecimal ( days ) ); // Extract the fractional number, separate from the integer number.
BigDecimal secondsFractional = new BigDecimal ( TimeUnit.DAYS.toSeconds ( 1 ) ).multiply ( fractionOfADay );
long secondsWhole = secondsFractional.longValue ( );
long nanos = secondsFractional.subtract ( new BigDecimal ( secondsWhole ) ).multiply ( new BigDecimal ( 1_000_000_000L ) ).longValue ( );
Duration duration = Duration.ofDays ( days ).plusSeconds ( secondsWhole ).plusNanos ( nanos );
Instant instant = epoch.plus ( duration );
return instant;
}
走向另一个方向。
public BigDecimal convertInstantToModifiedJulianDays ( Instant instant ) {
Instant epoch = OffsetDateTime.of ( 1858, 11, 17, 0, 0, 0, 0, ZoneOffset.UTC ).toInstant ( ); // TODO: Make into a constant to optimize.
Duration duration = Duration.between ( epoch, instant );
long wholeDays = duration.toDays ( );
Duration durationRemainder = duration.minusDays ( wholeDays );
BigDecimal wholeDaysBd = new BigDecimal ( wholeDays );
BigDecimal partialDayInNanosBd = new BigDecimal ( durationRemainder.toNanos ( ) ); // Convert entire duration to a total number of nanoseconds.
BigDecimal nanosInADayBd = new BigDecimal ( TimeUnit.DAYS.toNanos ( 1 ) ); // How long is a standard day in nanoseconds?
int scale = 9; // Maximum number of digits to the right of the decimal point.
BigDecimal partialDayBd = partialDayInNanosBd.divide ( nanosInADayBd ); // Get a fraction by dividing a total number of nanos in a day by our partial day of nanos.
BigDecimal result = wholeDaysBd.add ( partialDayBd );
return result;
}
调用这些转换方法。
BigDecimal input = new BigDecimal ( "57831.5" );
Instant instant = this.convertModifiedJulianDaysToInstant ( input );
BigDecimal output = this.convertInstantToModifiedJulianDays ( instant );
转储到控制台。
System.out.println ( "input.toString(): " + input );
System.out.println ( "instant.toString(): " + instant );
System.out.println ( "output.toString(): " + output );
input.toString():57831.5
instant.toString():2017-03-19T12:00:00Z
output.toString():57831.5
查看所有code running live at IdeOne.com。
此外,my Answer对类似问题可能会有所帮助。