Oracle 电子邮件发送日期有时不正确

Posted

技术标签:

【中文标题】Oracle 电子邮件发送日期有时不正确【英文标题】:Oracle email send date some times are not correct 【发布时间】:2014-05-09 15:32:39 【问题描述】:

我有一个与 Oracle 11g 电子邮件发送日期相关的生产问题。代码如下。

  procedure email(p_recip   in apex_application_global.vc_arr2,
                  p_subject in varchar2,
                  p_message in varchar2) is

    c                  utl_smtp.connection;
    msg                varchar2(4000);
    username           varchar2(100) := 'XXX';
    password           varchar2(100) := '123';
    l_encoded_username varchar2(200);
    l_encoded_password varchar2(200);
    l_recips           varchar2(2000);

    procedure send_header(name in varchar2, header in varchar2) as
    begin
      utl_smtp.write_data(c, name || ': ' || header || utl_tcp.crlf);
    end;
  begin
    --Open SMTP connection
    l_encoded_username := utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw(username)));
    l_encoded_password := utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw(password)));
    c                  := utl_smtp.open_connection('AAA.BBB.local', '25');
    utl_smtp.ehlo(c, 'AAA.BBB.local'); --DO NOT USE HELO
    utl_smtp.command(c, 'AUTH', 'LOGIN');
    utl_smtp.command(c, l_encoded_username);
    utl_smtp.command(c, l_encoded_password);
    utl_smtp.mail(c, 'XXX@YYY.on.ca');

    if ((p_recip is null) or p_recip.count = 0) then
      return;
    end if;

    for i in 1 .. p_recip.count loop
      utl_smtp.rcpt(c, p_recip(i));
      l_recips := l_recips || p_recip(i) || ','; --mark as Multiple receivers
    end loop;

    --now remove the trailing comma at the end of l_recips
    l_recips := substr(l_recips, 0, length(l_recips) - 1);

    utl_smtp.open_data(c);

    --prepare mail header
    utl_smtp.write_data(c, 'Date: ' ||
                         to_char(sysdate, 'MM-DD-YYYY HH24:MI:SS') ||
                         utl_tcp.crlf);

    utl_smtp.write_data(c, 'To: ' || l_recips || utl_tcp.crlf);

    utl_smtp.write_data(c, 'From: ' ||
                         '"Company" <MMM@KKK.on.ca>' ||
                         utl_tcp.crlf);
    utl_smtp.write_data(c, 'Subject: ' || p_subject || utl_tcp.crlf ||
                         utl_tcp.crlf);

    --include the message body
    utl_smtp.write_data(c, msg);

    -- Write message body
    utl_smtp.write_data(c, p_message || utl_tcp.crlf);

    -- Clean up
    utl_smtp.close_data(c);
    utl_smtp.quit(c);

  exception
    when utl_smtp.transient_error or utl_smtp.permanent_error then
      begin
        utl_smtp.quit(c);
      exception
        when utl_smtp.transient_error or utl_smtp.permanent_error then
          null;
          -- When the SMTP server is down or unavailable, we don't have
        -- a connection to the server. The QUIT call will raise an
        -- exception that we can ignore.
      end;

      raise_application_error(-20000, 'Failed to send mail due to the following error: ' ||
                               sqlerrm);
  end;

------------------------------------------

Unfortunately, the date on received email "Sent:" some times are wrong.  
For example :

【问题讨论】:

一个是您数据库中的日期 - 一个是来自电子邮件服务器的日期... 【参考方案1】:

RFC 822 中指定了数据格式(RFC1123 中添加了四位数年份)。您正在以不同的格式发送日期。看起来这有时被接受,有时不被接受;并给出“错误”的结果。文件夹视图中的日期是接收日期,而不是发送日期,因此它实际上没有任何影响 - 只是它与邮件正文中的日期一致,作为有用的交叉引用。

你正在这样做:

utl_smtp.write_data(c, 'Date: ' ||
                     to_char(sysdate, 'MM-DD-YYYY HH24:MI:SS') ||
                     utl_tcp.crlf);

所以现在我会得到Date: 05-09-2014 17:43:28 的值。根据应该被解释为2014-09-05(9 月 5 日)的 RFC。您似乎有时会看到这一点,但并非总是如此,这表明 MTA 的处理方式有所不同。基于这三个示例,这可能取决于“错误”日期是否有效。如果我发送带有该日期的邮件,Outlook 中确实会出现2014-09-05;但是,如果我将其更改为 05-23-2014 17:43:28,那么它会出现正确的日期 - 大概是因为无效日期 2014-23-05 被默默拒绝,而 MTA 使用其当前日期。 (或者客户会这样做;事实上这可能更有可能)。

您在第二个示例中似乎有进一步的转折,因为它显示了06/10/2013,而不是您可能期望的“正确”日期06/11/2013。我认为那是因为您也失去了时区偏移;事实上,您并没有设置一个,但是当它被调整时,您可能仍然会跨越一个日期边界。

因此,按照 RFC 的要求格式化您的日期:

utl_smtp.write_data(c, 'Date: ' ||
                     to_char(systimestamp, 'Dy DD Mon YYYY HH24:MI:SS TZH:TZM',
                         'NLS_DATE_LANGUAGE=ENGLISH') ||
                     utl_tcp.crlf);

这给出了:

Date: Fri 09 May 2014 17:47:52 +01:00

在我的 BST 时区。请注意,您必须使用systimestamp 而不是sysdate 来获取时区信息。而且它必须是英文的,所以我在to_char() 中添加了可选的第三个参数,以确保无论会话区域设置如何。


@ShoeLace 在 cmets 中指出,上面的字符串仍然与 RFC 822 不完全匹配,它可能应该是:

utl_smtp.write_data(c, 'Date: ' ||
                     to_char(systimestamp, 'Dy, DD Mon YYYY HH24:MI:SS TZHTZM',
                         'NLS_DATE_LANGUAGE=ENGLISH') ||
                     utl_tcp.crlf);

现在给出:

Date: Tue, 04 Aug 2015 10:09:31 +0100

有些服务器可以接受不带逗号或冒号的,但最好是正确的...

【讨论】:

感谢Alex提供详细的解决方案! @Matthew - 没问题;我刚刚添加了一个小改动,以确保它始终以英文发送,因为这也可能导致问题。 rfc0822 说 ' ( ("+" / "-") 4DIGIT ) ;局部微分;小时+分钟。 (HHMM)' 所以我认为时区偏移中不应该有冒号.. ? 并指定日期名称后应该有一个逗号。所以可能更像TO_CHAR(systimestamp, 'Dy "," DD Mon YYYY HH24:MI:SS TZHTZM','NLS_DATE_LANGUAGE=ENGLISH') @ShoeLace - 优点;现在不确定我当时是否真的测试过这个;如果我做了一些 SMTP 服务器可能比其他服务器更宽容。谢谢。【参考方案2】:

只是为@alex-poole 的答案添加一些额外的细节。

STMP 日期格式在http://www.ietf.org/rfc/rfc0822.txt 的第 5 节中指定:

5.1.  SYNTAX

 date-time   =  [ day "," ] date time        ; dd mm yy
                                             ;  hh:mm:ss zzz

 day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"
             /  "Fri"  / "Sat" /  "Sun"

 date        =  1*2DIGIT month 2DIGIT        ; day month year
                                             ;  e.g. 20 Jun 82

 month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"
             /  "May"  /  "Jun" /  "Jul"  /  "Aug"
             /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"

 time        =  hour zone                    ; ANSI and Military

 hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT]
                                             ; 00:00:00 - 23:59:59

 zone        =  "UT"  / "GMT"                ; Universal Time
                                             ; North American : UT
             /  "EST" / "EDT"                ;  Eastern:  - 5/ - 4
             /  "CST" / "CDT"                ;  Central:  - 6/ - 5
             /  "MST" / "MDT"                ;  Mountain: - 7/ - 6
             /  "PST" / "PDT"                ;  Pacific:  - 8/ - 7
             /  1ALPHA                       ; Military: Z = UT;
                                             ;  A:-1; (J not used)
                                             ;  M:-12; N:+1; Y:+12
             / ( ("+" / "-") 4DIGIT )        ; Local differential
                                             ;  hours+min. (HHMM)

所以你需要正确格式化你的日期

utl_smtp.write_data(c, 'Date: ' ||
                 to_char(systimestamp, 'Dy "," DD Mon YYYY HH24:MI:SS TZHTZM',
                     'NLS_DATE_LANGUAGE=ENGLISH') ||
                 utl_tcp.crlf);

这是一个快速查询,以每小时偏移量显示当前系统时间:

with generater as
 (select rownum-13 x from dual connect by level < 28 )
  select x, TO_CHAR(systimestamp at time zone x||':00', 'Dy "," DD Mon YYYY HH24:MI:SS TZHTZM','NLS_DATE_LANGUAGE=ENGLISH')
 from generater;

(仅供参考,ORA-01874:时区小时必须介于 -12 和 14 之间)。

【讨论】:

以上是关于Oracle 电子邮件发送日期有时不正确的主要内容,如果未能解决你的问题,请参考以下文章

我用的是foxmail邮箱,可以发送邮件给别人,收到别人发来的邮件,但是别人发不了给我,显示为无效收件人

FOXMAIL 不能发邮件

邮件为啥发不出去?

邮件发送失败的原因是啥?为何总是发不出去呢?

Oracle发最后通牒:要用 Java 8 更新先交钱

outlook2007,每次发邮件都提示“无法验证您连接到的服务器使用的安全证书。目标主要名称不正确”,求助?