Monthly Archive for September, 2010

不同程序语言的时间格式字符串

所谓”时间格式字符串”就是Datetime ( Date/ Time) 数据类型和字符串之间相互转换时用的placeholder. 例如:

<?php
date_default_timezone_set("UTC");
echo date('Y-m-d'); //输出当前时间年月日: 2010-10-01
?>

大部分程序语言都提供像 ‘Y’, ‘m’, ‘d’ 这种时间格式字符串. Unfortunately, 不同语言表示时间字符串格式是类似而又不同的, 例如表示完整年份(e.g. “2010”), 不同语言可能使用”Y”, “YYYY” 或 “yyyy”, 极易互相混淆. 基本上除了经常查文档没有什么好办法.

以下整理一张不同程序语言时间格式字符串对照表格, 方便查看和记忆:

Datetime Example PHP MySQL Oracle Java
Full Year (4 digits) 2010 Y %Y YYYY yyyy
Year (last 2 digits) 98, 05 y %y YY yy
Month Num (2 digits with leading zero) 02, 11 m %m mm MM
Month Num (no leading zero) 2, 11 n %c M
Month Abbreviation (3 letters) Jan, Dec M %b Mon MMM
Month Fullname January, December F %M Month MMMMM
Day Num (2 digits with leading zeros) 06, 26 d %d dd dd
Day Num (without leading zeros) 6, 26 j %e d
Day with ordinal suffix of montd 0th, 1st, 2nd, 3rd, 26th   jS %D FMddtd
Day Abbreviation in week (3 letters) Mon, Tue, Sun D %a Dy EEE
Day Fullname in week Sunday, Saturday l %W Day EEEEE
Hour (12-hour format, with leading zero) 01, 12 h %h or %I HH or HH12 hh
Hour (12-hour format, without leading zero)   1, 12 g %l FMHH or FMHH12   h
Hour (24-hour format, with leading zero) 00, 23 H %H HH24 HH
Hour (24-hour format, without leading zero)   0, 23 G %k FMHH24 H
AM / PM (Uppercase) AM, PM A %p a
AM / PM (Lowercase) am, pm a
Minutes (with leading zero) 00, 59 i %i MI mm
Minutes (without leading zero) 0, 59 j FMMI m
Seconds (2 letters with leading zero) 00, 59 s %s or %S   SS ss
Seconds (2 letters without leading zero) 0, 59 FMSS s
Timestamp (in seconds) a very long integer U
Timezone +0600, -1000 O Z

其它时间格式:
RFC-2822: “Wed, 11 Jan 1984 05:00:00 +0000”, php的 ‘r’ 格式符
ISO-8601: “2004-02-12T15:19:21+00:00”, php的 ‘c’ 格式符

(HTTP header中Last-Modified等时间字段格式是RFC变种 = =: “Wed, 11 Jan 1984 05:00:00 GMT”)

说明:
1. PHP:
输出Datetime为字符串:

<?php
date_default_timezone_set("UTC"); // 参数为时区标识符 如Atlantic/Azores
echo date($format = 'Y-m-d', $time); //输出UTC时间字符串, $time为Unix时间戳(秒数).
?>

时间格式符中可以用 ” 转义 (literal字符串中要写成 ‘\’ )

2. MySQL:
DATETIME字段类型

/* 查询 时间指定格式 */
SELECT DATE_FORMAT(datetimefield, '%W %M %Y') from sth;

/* 直接select时间字段, 返回 '2009-10-04 22:23:00'格式 */
SELECT datetimefield from sth;

/* 插入时间 */
insert into tableName(datetimefield) values('2009-10-04 22:23:00');

DATE和TIME字段格式则分别为’2009-10-04’和’22:23:00′

转义符是’%’, ‘%%’表示字面上的一个’%’字符

3. Oracle
Oracle的时间格式字符串大小写影响返回时间大小写. FM只能出现在开头, 表示去掉时间中所有前导零和空白

select to_char(sysdate, 'YYYY/mm/dd') from sth; /*返回 2010/01/08*/
select to_char(sysdate, 'FMYYYY/mm/dd') from sth; /*返回 2010/1/8*/

select to_char(sysdate, 'Month') from sth; /* 返回 'July' */
select to_char(sysdate, 'month') from sth; /* 返回 'july' */
select to_char(sysdate, 'MONTH') from sth; /* 返回 'JULY' */

/* 插入时间 */
insert into tableName(datetimefield) values( to_date('2010-06-17 04:14:25', 'YYYY-mm-dd HH24:MI:SS') );

4. Java

import java.text.SimpleDateFormat;
import java.util.Date;

Date d = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = f.format(d);
Date d2 = f.parse(s);

XMPP服务器, BOSH(Http-Binding)和WEB客户端搭建

自用笔记, 别当教程.

目标: 搭建一个XMPP服务器, 实现在web page上用javascript与自己XMPP服务器通信, 匿名登录并与任何一个XMPP(Jabber)帐户通信. (Gtalk目前尚有问题)

XMPP服务器可能不是必须的(见下文, 我没有尝试)

环境与配置:

XMPP服务器: ejabberd 文档
HTTP-Binding: 使用ejabberd搭建, 5280端口.
Javascript Client: Strophe 文档

安装Ejabberd

yum install ejabberd
#apt-get install ejabberd

编辑配置文件: /etc/ejabberd/ejabberd.cfg, 这是个era lang格式配置文件, 行注释符号是%. 请参考ejabberd文档.

下面是默认配置文件里我修改过部分:

%%debug
{loglevel, 5}.
{hosts, ["sagan.me"]}.
{host_config, "sagan.me", [{auth_method, [anonymous,internal]},{anonymous_protocol, sasl_anon}]}.

{listen,
 [
  {5222, ejabberd_c2s, [
                        {certfile, "/path/to/ssl/cert.pem"},
                        %%starttls,
                        starttls_required,
                        {access, c2s},
                        {shaper, c2s_shaper},
                        {max_stanza_size, 65536}
                       ]},
  {5269, ejabberd_s2s_in, [
                           {shaper, s2s_shaper},
                           {max_stanza_size, 131072}
                          ]},
  {{5280, "127.0.0.1"}, ejabberd_http, [
                        {request_handlers, [{["http-bind"], mod_http_bind}]},
                         captcha
                        ]}

 ]}.
{s2s_use_starttls, true}.
{s2s_certfile, "/path/to/ssl/cert.pem"}.
{s2s_default_policy, allow}.
{auth_method, [internal, anonymous]}.

上面配置中, 声明监听127.0.0.1(本地IP地址) 5280端口为http-binding (BOSH)服务地址, 路径是”http-bind”, 即服务实际URI是”http://127.0.0.1:5280/http-bind”. 然后需要在web服务器配置中用mod_proxy或mod_rewrite将80或443端口上对 “/http-bind” 访问转发到”http://127.0.0.1:5280/http-bind”, 因为由于浏览器同源限制, yourdomain.com:80上的web page是无法直接向yourdomain.tld:5280提交ajax请求的. ( 所以在上面配置中把ejabberd http-bind监听的端口设为了127.0.0.1:5280, 即不能从外部直接访问)

添加域名DNS SRV记录

这一步是必须的, 否则搭建的XMPP服务器基本上无法与大多数其它服务器或客户端通信. (插一句: Google Apps Talk基于XMPP平台, 如果不设置域名SRV记录的话, 就只能够用Gtalk登录(无法使用其它XMPP客户端), 而且只能和gmail.com或其它Google Apps域名的帐户通信)

_xmpp-client._tcp.sagan.me. 86400 IN SRV 10 0 5222 sagan.me.
_xmpp-server._tcp.sagan.me. 86400 IN SRV 10 0 5269 sagan.me.

5269和5222是XMPP在ICANN注册的标准端口.

修改WEB服务器配置

我的Lighttpd ModProxy配置:

proxy.server = (
        "/http-bind" => ( (
                "host" => "127.0.0.1",
                "port" => 5280
        ) )
)

应该也可以用web服务器直接转发请求到外部某个公开的Jabber (XMPP)服务器 http-bind地址, 我没有尝试. (基本上找不到公开的提供http-bind的XMPP服务器)

使用Javascript客户端

上面ejabberd配置里开启了匿名登录(ANOYMOUS mechanism), 最终目的就是为了在web page中匿名访问服务并向任何一个XMPP帐户发送消息.

下载Strophe JS库并上传到你的域名目录下(这个库只有一个文件strophe.js), 下面这个测试例子修改自Strophe examples目录下echobot.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Strophe.js Echobot Example</title>
  <script type='text/javascript'
          src='http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js'></script>
  <script type='text/javascript'
          src='../strophe.js'></script>
  <script type='text/javascript'
          src='echobot.js'></script>
</head>
<body>
  <div id='login' style='text-align: center'>
    <form name='cred'>
      <label for='jid'>JID:</label>
      <input type='text' id='jid' value="sagan.me" />
      <label for='pass'>Password:</label>
      <input type='password' id='pass' />
      <input type='button' id='connect' value='connect' />
    </form>
  </div>
  <hr />
  <div id='log'></div>
</body>
</html>

看echobot.js

var BOSH_SERVICE = '/xmpp-httpbind';
var connection = null;

function log(msg) 
{
    $('#log').append('<div></div>').append(document.createTextNode(msg));
}

function onConnect(status)
{
    if (status == Strophe.Status.CONNECTING) {
	log('Strophe is connecting.');
    } else if (status == Strophe.Status.CONNFAIL) {
	log('Strophe failed to connect.');
	$('#connect').get(0).value = 'connect';
    } else if (status == Strophe.Status.DISCONNECTING) {
	log('Strophe is disconnecting.');
    } else if (status == Strophe.Status.DISCONNECTED) {
	log('Strophe is disconnected.');
	$('#connect').get(0).value = 'connect';
    } else if (status == Strophe.Status.CONNECTED) {
	log('Strophe is connected.');
	log('ECHOBOT: Send a message to ' + connection.jid + 
	    ' to talk to me.');

	connection.addHandler(onMessage, null, 'message', null, null,  null); 
	connection.send($pres().tree());
	var reply = $msg({to: "[email protected]", from: connection.jid, type: 'chat'}).c("body").t("Test Chat Message");
        connection.send(reply.tree());
    }
}

function onMessage(msg) {
    var to = msg.getAttribute('to');
    var from = msg.getAttribute('from');
    var type = msg.getAttribute('type');
    var elems = msg.getElementsByTagName('body');

    if (type == "chat" && elems.length > 0) {
	var body = elems[0];

	log('ECHOBOT: I got a message from ' + from + ': ' + 
	    Strophe.getText(body));
    
	var reply = $msg({to: from, from: to, type: 'chat'})
            .cnode(Strophe.copyElement(body));
	connection.send(reply.tree());

	log('ECHOBOT: I sent ' + from + ': ' + Strophe.getText(body));
    }

    // we must return true to keep the handler alive.  
    // returning false would remove it after it finishes.
    return true;
}

$(document).ready(function () {
    connection = new Strophe.Connection(BOSH_SERVICE);

    // Uncomment the following lines to spy on the wire traffic.
    //connection.rawInput = function (data) { log('RECV: ' + data); };
    //connection.rawOutput = function (data) { log('SEND: ' + data); };

    // Uncomment the following line to see all the debug output.
    //Strophe.log = function (level, msg) { log('LOG: ' + msg); };


    $('#connect').bind('click', function () {
	var button = $('#connect').get(0);
	if (button.value == 'connect') {
	    button.value = 'disconnect';

	    connection.connect($('#jid').get(0).value,
			       $('#pass').get(0).value,
			       onConnect);
	} else {
	    button.value = 'connect';
	    connection.disconnect();
	}
    });
});

[email protected]修改为一个测试Jabber帐号. 然后用浏览器打开echobot.html, 点击Connect按钮, Strophe就会匿名登录到刚刚建立的ejabber服务器( sagan.me ), 并向 “[email protected]”这个帐号发送一条”Test Message”的信息.

另: 测试匿名登录向[email protected] 发送消息失败, log里显示Gtalk服务器返回信息是503 error, Service-Unavailable, 但如果正常登录并添加Gtalk为好友的话则可以. Gtalk禁止了匿名用户向其发送消息? 我还在查资料中.

我准备用Javascript写一个简单的XMPP WEB匿名客户端, 实现允许访客直接与Gtalk和Facebook Chat通信等功能.

终于见到传说中Google的小范围测试搜索新功能

Google经常会在正式发布新功能和特性之前小范围测试一下(如之前的图片搜索结果无翻页, Instant Search). 今天我终于遇到了一次. = =

这就是传说中的搜索结果页居中显示. 据说只加了一行”margin: 0 auto;” CSS代码. 不知道在高宽度分辨率显示器下效果如何. (比如Mac 27寸 2560×1440像素的LED)

Google gdata javascript API doesn’t support Picasa?

Oh, I just sadly find that Google gdata javascript API does not include support for Picasa = =.

There is a Picasa API which is in REST API style. However, it’s not easy to access it via javascript because of the following reasons:

  1. The browser’s same origin restriction and:
  2. Picasa API use only XML as submit and return data formats, no jsonp.

As some of Picasa GET APIs are atom feeds, you can use Google Ajax Feed API or Yahoo Query Language(YQL) to get and access them. However, it’s difficulty to do more things purely via javascript.

5000 Characters Length Limit Of Google Ajax Language API

Google Ajax Language API allow translating and detecting the language of blocks of text within a webpage using only JavaScript. However, it has a characters length limit of 5000 for input text (see it’ terms). Although you should even can’t translate a text longer than about 1000 characters if you don’t use a Javascript Proxy to solve Cross Domain problems and transfer POST request, because the limit of URL length.

I have written a simple Javascript Proxy in PHP, it only accepts POST method. (Most javascript APIs allow using jsonp in GET method.)

<?php
// This very Simple PHP Proxy is degigned to solve javascript browser cross domain (same origin policy) problems

if ( 'POST' != $_SERVER['REQUEST_METHOD'] ) {
	header('Allow: POST');
	header('HTTP/1.1 405 Method Not Allowed');
	die();
}

function fail($error) {
	header('HTTP/1.1 500 Internal Server Error');
	die($error);
}

if( !isset($_SERVER['HTTP_HOST']) )
	fail("Forbidden");

$allowed_domains = Array($_SERVER['HTTP_HOST']);
if( 
	!isset($_SERVER["HTTP_REFERER"]) ||
	!preg_match('#^https?://(' . implode("|", array_map('preg_quote', $allowed_domains)) . ')#i', $_SERVER["HTTP_REFERER"]) 
)
	fail("Referrer Empty or Not Allowed.");

if( !isset($_GET['url'])  )
	fail("Forbidden");

$session = curl_init($_GET['url']);

$postvars = '';
$deli = '';
while ($element = current($_POST)) {
	$postvars .= $deli . urlencode(key($_POST)).'='.urlencode($element);
	next($_POST);
	$deli = '&';
}

curl_setopt ($session, CURLOPT_POST, true);
curl_setopt ($session, CURLOPT_POSTFIELDS, $postvars);


curl_setopt($session, CURLOPT_HEADER, false);
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
curl_setopt($session, CURLOPT_REFERER, $_SERVER["HTTP_REFERER"]);
curl_setopt($session, CURLOPT_SSL_VERIFYPEER, false);


$result = curl_exec($session);


$headers = array(
	'Expires' => 'Wed, 11 Jan 1984 05:00:00 GMT',
	'Last-Modified' => gmdate( 'D, d M Y H:i:s' ) . ' GMT',
	'Cache-Control' => 'no-cache, must-revalidate, max-age=0',
	'Pragma' => 'no-cache',
	'Content-Type' => 'text/javascript; charset=UTF-8',
	'Content-Length' => strlen($result)
);
foreach( $headers as $name => $field_value )
	header("{$name}: {$field_value}");

echo $result;
curl_close($session);

Usage:
Just send your POST request to yourproxy.php?url=POSTURL instead of directly to POSTURL.

I used this proxy to develop a feature that translate the whole webpage into another language using Google Ajax Language API. Sadly I found the Google’s Limit that if you POST a text longer then 5000 characters, Google Server will return a error “exceed string length limit”. I am exploring other open translation APIs, such as Microsoft Translator AJAX API. However, Microsoft’s Docs seems to be more complex ( and ugly = = ) than Google’s.

BTW: why google and other web APIs which can be accessed via javascript don’t support Cross-Origin Resource Sharing? It seems currently only Twitter and YQL support this standand.

Back to WordPress

As of now I migrate my several blogs into here, sagan.me. Also I’m back to WordPress (from Movable Type) !

This blog (sagan.me) has posts written in different languages ( such as English, Chinese, Japanese and French). By default you will only see posts which are written in your browser’s preferred language in homepage. You can visit /?lr= to see posts in all languages. I will add UI language selector to blog soon.

By the way, the “lr” parameter is from Google Search’s same parameter, meaning “language restriction”. I’m a Google fan! Surely you can manually set value of it, like /?lr=ja, which will show my Japanese posts.