From f95bfa4100f92e91e631979dbb48ffb6961fc585 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@factorcode.org>
Date: Sat, 16 Feb 2008 14:59:01 -0600
Subject: [PATCH] Add timezone support to RFC3339 parser

---
 extra/calendar/calendar-tests.factor | 22 +++++++++++-
 extra/calendar/calendar.factor       | 51 ++++++++++++++++++++--------
 2 files changed, 57 insertions(+), 16 deletions(-)
 mode change 100644 => 100755 extra/calendar/calendar-tests.factor

diff --git a/extra/calendar/calendar-tests.factor b/extra/calendar/calendar-tests.factor
old mode 100644
new mode 100755
index 3b0cfc8455..a3ae5f115a
--- a/extra/calendar/calendar-tests.factor
+++ b/extra/calendar/calendar-tests.factor
@@ -1,5 +1,5 @@
 USING: arrays calendar kernel math sequences tools.test
-continuations system ;
+continuations system io.streams.string ;
 
 [ 2004 12 32 0   0  0 0 make-timestamp ] [ "invalid timestamp" = ] must-fail-with
 [ 2004  2 30 0   0  0 0 make-timestamp ] [ "invalid timestamp" = ] must-fail-with
@@ -141,3 +141,23 @@ continuations system ;
 [ t ] [ 0 unix-time>timestamp unix-1970 = ] unit-test
 [ t ] [ 123456789 [ unix-time>timestamp timestamp>unix-time ] keep = ] unit-test
 [ t ] [ 123456789123456789 [ unix-time>timestamp timestamp>unix-time ] keep = ] unit-test
+
+[ 0 ] [
+    "Z" [ read-rfc3339-gmt-offset ] with-string-reader
+] unit-test
+
+[ 1 ] [
+    "+01" [ read-rfc3339-gmt-offset ] with-string-reader
+] unit-test
+
+[ -1 ] [
+    "-01" [ read-rfc3339-gmt-offset ] with-string-reader
+] unit-test
+
+[ -1-1/2 ] [
+    "-01:30" [ read-rfc3339-gmt-offset ] with-string-reader
+] unit-test
+
+[ 1+1/2 ] [
+    "+01:30" [ read-rfc3339-gmt-offset ] with-string-reader
+] unit-test
diff --git a/extra/calendar/calendar.factor b/extra/calendar/calendar.factor
index a8d76ac86f..5b89d6e8c5 100755
--- a/extra/calendar/calendar.factor
+++ b/extra/calendar/calendar.factor
@@ -373,32 +373,53 @@ M: timestamp year. ( timestamp -- )
     #! Example: Tue, 15 Nov 1994 08:12:31 GMT
     >gmt timestamp>rfc822-string ;
 
+: write-rfc3339-gmt-offset ( n -- )
+    dup zero? [ drop "Z" write ] [
+        dup 0 < [ CHAR: - write1 neg ] [ CHAR: + write1 ] if
+        60 * 60 /mod swap write-00 CHAR: : write1 write-00
+    ] if ;
+
 : (timestamp>rfc3339) ( timestamp -- )
     dup timestamp-year number>string write CHAR: - write1
     dup timestamp-month write-00 CHAR: - write1
     dup timestamp-day write-00 CHAR: T write1
     dup timestamp-hour write-00 CHAR: : write1
     dup timestamp-minute write-00 CHAR: : write1
-    timestamp-second >fixnum write-00 CHAR: Z write1 ;
+    dup timestamp-second >fixnum write-00
+    timestamp-gmt-offset write-rfc3339-gmt-offset ;
 
 : timestamp>rfc3339 ( timestamp -- str )
-    >gmt [ (timestamp>rfc3339) ] with-string-writer ;
+    [ (timestamp>rfc3339) ] with-string-writer ;
 
-: expect read1 assert= ;
+: expect ( str -- )
+    read1 swap member? [ "Parse error" throw ] unless ;
+
+: read-00 2 read string>number ;
+
+: read-0000 4 read string>number ;
+
+: read-rfc3339-gmt-offset ( -- n )
+    read1 dup CHAR: Z = [ drop 0 ] [
+        { { CHAR: + [ 1 ] } { CHAR: - [ -1 ] } } case
+        read-00
+        read1 { { CHAR: : [ read-00 ] } { f [ 0 ] } } case
+        60 / + *
+    ] if ;
 
 : (rfc3339>timestamp) ( -- timestamp )
-    4 read string>number ! year
-    CHAR: - expect
-    2 read string>number ! month
-    CHAR: - expect
-    2 read string>number ! day
-    CHAR: T expect
-    2 read string>number ! hour
-    CHAR: : expect
-    2 read string>number ! minute
-    CHAR: : expect
-    2 read string>number ! second
-    0 <timestamp> ;
+    read-0000 ! year
+    "-" expect
+    read-00 ! month
+    "-" expect
+    read-00 ! day
+    "Tt" expect
+    read-00 ! hour
+    ":" expect
+    read-00 ! minute
+    ":" expect
+    read-00 ! second
+    read-rfc3339-gmt-offset ! timezone
+    <timestamp> ;
 
 : rfc3339>timestamp ( str -- timestamp )
     [ (rfc3339>timestamp) ] with-string-reader ;