使用Java邮件API发送日历条目时出现时区问题

时间:2016-08-01 08:31:02

标签: javamail icalendar

我们正在使用JavaMail API发送日历条目。但Outlook的收件人有时区问题,因为会议显示错误的时间。一般来说,我们的方法如下:

首先我们有,  SimpleDateFormat iCalendarDateFormat = new SimpleDateFormat(" yyyyMMdd' T' HHmmss");

然后我们使用iCalendarDateFormat.setTimeZone(TimeZone.getTimeZone(receiverTimeZone));

最后,我们使用Calendar.getInstance()来开始和结束操作Calendar字段, 因此我们有Date startDate = startTime.getTime();             日期endDate = endTime.getTime();

当我们要根据icalendar规范发送请求时,我们有 " DTSTAMP:" + iCalendarDateFormat.format(startDate)+" \ n" + " DTSTART:" + iCalendarDateFormat.format(startDate)+" \ n" " DTEND:" + iCalendarDateFormat.format(endDate)+" \ n"

这是正确的做法吗?请评论。

由于

3 个答案:

答案 0 :(得分:1)

tl; dr

iCalendar格式与其预期时区分开跟踪日期时间。您必须适当地处理两个部分。

始终使用 java.time 类。切勿使用CalendarSimpleDateFormat之类的旧类。

详细信息

注意:我之前没有使用过 iCalendar 数据。因此,我的理解可能不正确。

pages 31-33 of the RFC 5545 spec看,该规范的作者似乎假设您始终希望将日期时间与时区分开记录。

时刻,即时间轴上的一点,需要时区或UTC偏移量的上下文。例如,“明年2021年1月23日中午”不是一个时刻。我们不知道您是指日本东京的中午,法国图卢兹的中午还是美国俄亥俄州的托莱多中午-都是非常不同的时刻,相隔几个小时。

要提供偏移量的上下文,日期和时间必须带有若干小时-分钟-秒,例如08:00。对于零时分秒,请使用+00:00

2021-01-23T12:00:00 + 00:00

作为零位偏移量+00:00的缩写,可以使用字母Z,发音为“ Zulu”。例如:

2021-01-23T12:00:00Z

但是,奇怪的是,iCalendar规范想要跟踪与时区分开的日期和时间。所以这个:

2021-01-23T12:00:00

...以及其他地方的时区字段:

美国/纽约

并且iCalendar规范选择了ISO 8601允许的难以理解的“基本”变体,从而最大程度地减少了定界符的使用。所以这个:

20210123T120000

对于这样的字符串,我们必须将其解析为LocalDateTime。此类表示带有日期的日期,但缺少任何时区或UTC偏移量。

DateTimeFormatter f = dateTimeFormatter.ofPattern( "uuuuMMdd'T'HHmmss" ) ;
String input = "20210123T120000" ;  // “Basic” variation of ISO 8601 format.
LocalDateTime ldt = LocalDateTime.parse( input , f ) ;

要确定时刻,我们必须应用时区。我假设iCalendar使用proper time zone namesContinent/Region格式),而不是2-4个字母的伪区域,例如PSTCSTIST等。

String zoneName = receiverTimeZone ;  // Variable name taken from your code example, though you neglected to show its origins.
ZoneId z = ZoneId.of( zoneName ) ; 

应用区域以在时间轴上获得一个ZonedDateTime,一刻,一个点。

ZonedDateTime zdt = ldt.atZone( z ) ;

往另一个方向,让我们从当前时刻开始。

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime now = ZonedDateTime.now( z ) ;

并为iCalendar生成字符串值。

DateTimeFormatter f = dateTimeFormatter.ofPattern( "uuuuMMdd'T'HHmmss" ) ;
String iCal_DateTime = now.format( f ) ;
String iCal_ZoneName = now.getZone().toString() ;

请不要使用与Java最早的版本捆绑在一起的可怕的旧式日期时间类:CalendarGregorianCalendarjava.util.DateSimpleDateFormat,依此类推。几年前,这些已被JSR 310中定义的现代 java.time 类取代。

Table of date-time types in Java, both modern and legacy

答案 1 :(得分:0)

很难说没有看到iCalendar文件的实际内容,以及带有时区信息的预期开始和结束日期时间,但您似乎在浮动时间(具有本地时间的日期时间)生成DTSTART。虽然您的代码示例似乎暗示您可以访问收件人的时区(receiverTimezone),但这是一种非常脆弱的方法。

相反,您应该使用具有UTC时间的日期时间或具有本地时间和时区的日期时间(其中时区不必是接收器时区)。 如果事件不再发生,最简单的方法是使用UTC时间的日期时间。

有关每种格式的定义,请参阅https://tools.ietf.org/html/rfc5545#section-3.3.5

答案 2 :(得分:0)

我遇到了同样的问题,为此我苦苦挣扎。因此,下面是我的发现:

Outlook在UTC时区可以正常工作。如果我们使用UTC时区设置日期和时间,则Outlook会自动将此UTC时间转换为用户对应的Timezone。我们将不得不为DTSTART:, DTEND:DTSTAMP使用“即时”对象(可选,但建议使用)。

快速测试只需在ic字符串中使用"DTSTART:"+Instant.now()

在Java 8中用于获取UTC时间Java time API提供了Instant.now(),通过它您可以以UTC格式获取系统时间。 Java 8还提供了类似的方法

a. Instant.ofEpochMilli()-返回Instant,可以直接在ical Sting中使用。

b. new Date().toInstant()返回UTC即时对象。

在几种情况下,输入日期和时间源是不同的:

  1. 如果要从数据库中获取日期和时间,则在这种情况下,数据库不存储时区,而是唯一保存的日期和时间。因此,首先要在保存数据库的那个时区中转换日期和时间,在我的情况下,我是在“ EST”时区中转换后存储日期和时间,而日期值为EST,但时区不存在在数据库中。因此,在从数据库中获取日期和时间值时,我在日期值中附加了时区,然后使用以下方法进一步转换为EPOC时间

    public static long getEpocTimeWithTimezone(Date date) {
    
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    DateTimeFormatter dateTimePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime dateTime = LocalDateTime.parse(simpleDateFormat.format(date), dateTimePattern);
    
    long epochInMilliSeconds = dateTime.atZone(ZoneId.of("America/New_York")).toEpochSecond() * 1000;
    
    return epochInMilliSeconds;
     }
    

    然后仅对ical String使用以下代码:

      Instant startDt = Instant.ofEpochMilli(getEpocTimeWithTimezone(//pass your date here 
      )).truncatedTo(ChronoUnit.MINUTES);
    

    现在将此即时对象(startDt)直接设置为"DTSTART:"

     "DTSTART:"+startDt+"....then in same fashion "DTEND:" also.
    
  2. 在第二种情况下,您具有带时区的日期(请确保在转换后您不会丢失实际的时区,就像在第一种情况下,将Date保存在数据库中后,我们实际上丢失了时区,但显示的是时区IST那是假的,所以要小心点)

    因此,在这种情况下,只需假设myDateObject是Date对象。因此,只需获取Instant( 通过使用myDateObject类的toInstant(),将Date中的UTC对象放入。

      Instant startDt  = myDateObject.toInstant().truncatedTo(ChronoUnit.MINUTES);
    

    我正在使用.truncatedTo(ChronoUnit.MINUTES);,因为如果我们不使用它,那么 我们可能在“会议邀请时间”部分获得了额外的分钟或秒。

    因此,Outlook邮件的最终字符串应类似于:

    .
    .
    .
    "BEGIN:VEVENT\n"+
    "DTSTART:"+startDt+"\n"+
    "DTEND:"+endDt+"\n"+
    .
    .
    .
    

    VVI Note:由于Z是UTC时区的表示,因此仅在最后一个时间添加Z不会被UTC划分为时间,您必须进行转换日期和时间,那么Outlook只会显示准确的时间。要验证您的时间是否为UTC格式,只需将.ics附加文件(您从电子邮件中获取)保存在本地,然后检查日期和时间是否为DTSTART:2020-05-15T13:57:00Z,如果不是,则不转换日期正确地在UTC中。