Hello Derrell,

All I wanted to say was that  there's creating and parsing. My proposed approach would need one function each way.
Your current method does not need a function for parsing, but still for creating the string representation. If you have to put in this extra step anyway, why not stick to a ("the") standard string repesentation. You _are_ assuming that there is a function which creates this string representation, even on the _javascript_ side.

[...]
What you're saying is true: the original time zone is lost.  However, a Date
object itself doesn't know the time zone.  It retrieves that from the system
when you ask for a local-time-zone representation of the date.  What you _are_
guaranteed of when passing a date in UTC is that regardless of where it is
[...]

Sorry, I wasn't aware that _javascript_ Date objects don't actually store their timezone (which doesn't help them to get in my good books).

> On the server side, where you not only need to parse these kind of (let's
> say _javascript_-proprietary) strings but also construct them, you're
> introducing a new convention: Must be UTC. You need to start off with a UTC
> date and then construct from the different parts. I think this is an
> unnecessary requirement.

But as Andreas stated, what's important is not the server, it's the client.
We must do anything reasonable that we can to make the client-side processing
as fast as possible.

You seem to have two reasons for sticking with the "new Date(Date.UTC(...))" representation:
1. It's readily "eval()"able _javascript_ like the rest of JSON and therefore closer to the "standard"
2. It's readily "eval()"able _javascript_ and therefore a performance advantage (on the client)

Why bow to _javascript_'s deficiencies?

ad 1.: JSON is _a_ subset of _javascript_, and that subset doesn't include dates. There are several motivations behind JSON, and being "eval()"able is (just) one of them. I wouldn't count your constructor representation as a part of "an ideal data-interchange language". JSON-RPC extends JSON so that dates (or qxWidgets) can be interchanged as well.
ad 2.: This is true, and in my opinion the major tradeoff. If you create lots of Dates on the client and only a few on the server, the performance advantage will be neglectable, though. It's only worth it if it's the other way round, in which case there's more load on the server (which I agree is preferrable).


> http://mochikit.com/doc/html/MochiKit/DateTime.html
> "Remote servers don't give you _javascript_ Date objects, and they certainly
> don't want them from you, so you need to deal with string representations of
> dates and timestamps. MochiKit.Date does that."

I started off, a couple of weeks ago, agreeing with this.  I've since changed
my mind now that I understand fully what's going on here.  We are *supposed*
to be sending string-literal representations of everything, but there is no
string-literal representation of a date so we need to make do the best we
can.  The trade-off is to have something in the protocol that looks "ugly"
(new Date()) or to have to do extra parsing at the client.

IMHO, passing "QxDate(<ISO8601>)" is as ugly as passing "new Date()".
Furthermore, the server need not be qooxdoo-specific.  The current format can
be sent to any JSON-RPC client or between servers (which follow a few rules
which will be documented).  QxDate() or any local function adds requirements
to the client which I don't feel are appropriate.

That's a contradiction.
It is true for both approaches that a few (qooxdoo-specific) rules would need to be followed on both sides.

The "rule" for my proposed approach is "use ISO8601" which I find reasonable. ISO8601 is not qooxdoo-specific.
The rules that say must be _javascript_ constructor syntax and must be UTC I find less reasonable.


If it's difficult to convert your Python JSON-RPC server to use the format
we've been discussing, why don't you post it.  Maybe someone on the list can
help convert it to this format, and we might even be able to add it to the
tree.

I don't have a Python JSON-RPC server.
I'm currently developing a TurboGears application with a qooxdoo UI, and JSON dates will come into the equation soon.

Parser: If I wrap JSON in a parser object and ask it for the value of "mydate", I would have to do something like:
if type(mydate)==StringType and mydate.startswith('new Date(Date.UTC')
or (probably better) _try_ to convert _every_ string to a datetime using above method and leave it as a string if not possible.

A single ISO8601 string is easier to construct and parse than comma-separated constructor parameters, in every language other than _javascript_.

But I don't want to sit here and moan. :-)
Find attached a Python module for serializing/deserializing JSON Dates using your current format.

This could be made part of a Python JSON parser.
Such parsers usually provide hooks for __jsonclass__ handling, which is another argument for going that route...


> I had a look around, and everyone (including the examples on json.org)
> elegantly omits Date objects in JSON. :-) Various workarounds exist, and
> JSON-_RPC_ class hinting seems to be favored.
>
> My vote goes for a __jsonclass__ constructor "QXDate". Hopefully some more
> people will participate in this discussion.

So far, I've left off class hinting (other than in my original implementation,
and that was a very incomplete version of class hinting anyway) because I
haven't found an elegant way to implement it without either manual parsing of
the JSON or recursively descending through the object generated by eval()ing
the JSON to implement the class hints.  Please, if you can provide an
efficient way of handling this, let me know!

I can only see one other way, and that is to wrap the JSON string into a "JSON object" which provides the content. If a value is a simple data type, it's returned as that data type. If it's a string with __jsonclass__ "Date" (or whatever), a corresponding Date object is created and returned. The possibly nested stuff (JSON object, JSON array) would simply be handled by returning another "JSON object".

If you check the last comment on http://www.nikhilk.net/DateSyntaxForJSON.aspx
I think that's a nice way of doing it: Extend the Date object to allow serialization, and when serializing objects check whether the class has a "serialize" function; if it does, use it.


> Andreas, I remember you were concerned about client-side overhead. You're
> only saving the evaluation, not the creation.

Please explain more about what you mean here.  How are you generating an
ISO8601 string in a portable fashion (use Firefox as an example) other than by
obtaining each of the fields and generating the string yourself?

see above

Cheers,

Derrell

Kind regards,
Danny

--
Danny W. Adair
Director
Unfold Limited
New Zealand

Talk:       +64 4 472 1679
Fax:        +64 4 472 1680
Write:      [EMAIL PROTECTED]
Browse:     www.unfold.co.nz
Visit/Post: 22a Clifton Terrace, Kelburn 6005, Wellington, New Zealand

==============================
Caution
The contents of this email and any attachments contain information which is CONFIDENTIAL and may be subject to LEGAL PRIVILEGE. If you are not the intended recipient, you must not read, use, distribute, copy or retain this email or its attachments. If you have received this email in error, please notify us immediately by return email or collect telephone call and delete this email.  Thank you.  We do not accept any responsibility for any changes made to this email or any attachment after transmission from us.
==============================
#!/usr/bin/env python
"""
qooxdoo JSON Date support

Functions for serialization of datetime objects to JSON Dates in the format
'new Date(Date.UTC(year,month,day[,hour[,minute[,seconds[,milliseconds]]]]))'
and vice versa.

Full timezone support.

Version:     1.0
Author:      Danny W. Adair <[EMAIL PROTECTED]>
Last Change: 20 June 2006
"""
import datetime, time

# Timezone helpers
# See http://docs.python.org/lib/datetime-tzinfo.html

STDOFFSET = datetime.timedelta(seconds=-time.timezone)
DSTOFFSET = time.daylight and datetime.timedelta(seconds=-time.altzone) or STDOFFSET
DSTDIFF = DSTOFFSET - STDOFFSET

class LocalTimezone(datetime.tzinfo):
    """A class capturing the platform's idea of local time."""
    def utcoffset(self, dt):
        return self._isdst(dt) and DSTOFFSET or STDOFFSET
    def dst(self, dt):
        return self._isdst(dt) and DSTDIFF or datetime.timedelta(0)
    def tzname(self, dt):
        return time.tzname[self._isdst(dt)]
    def _isdst(self, dt):
        tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1)
        stamp = time.mktime(tt)
        tt = time.localtime(stamp)
        return tt.tm_isdst > 0
local_timezone = LocalTimezone()

class UTC(datetime.tzinfo):
    """UTC time zone."""
    def utcoffset(self, dt):
        return datetime.timedelta(0)
    def tzname(self, dt):
        return 'UTC'
    def dst(self, dt):
        return datetime.timedelta(0)
utc = UTC()

# JSON Date Serialization/Deserialization

class JSONDateError(Exception):
    """Exception for JSON parsing errors."""
    pass

def datetime2JSON(dt):
    """
    Converts the given datetime object to a string suitable for JavaScript evaluation:
    'new Date(Date.UTC(year,month,day[,hour[,minute[,seconds[,milliseconds]]]]))'

    If the datetime object is timezone-aware, it will be converted to UTC.
    If it is naive (= no timezone information), UTC will be assumed and applied.
    """
    # Apply UTC if datetime is naive, otherwise convert to UTC
    dt = (dt.tzinfo is None) and dt.replace(tzinfo = utc) or dt.astimezone(utc)
    return 'new Date(Date.UTC(%i,%i,%i,%i,%i,%i,%i))' % (dt.year, dt.month, dt.day, dt.hour,
                                                         dt.minute, dt.second, dt.microsecond / 1000)

def JSON2datetime(json_dt):
    """
    Parses json datetime string and returns a corresponding datetime object in UTC timezone.
    json_dt is expected to be in the format
    'new Date(Date.UTC(year,month,day[,hour[,minute[,seconds[,milliseconds]]]]))'
    or
    'null'

    A JSONDateError exception is raised if it isn't.
    Empty strings also raise this exception; use JavaScript's 'null' to indicate no date.
    'null' will return None.
    """
    if not json_dt:
        raise JSONDateError, 'Empty date - use "null" to indicate no date'
    elif json_dt=='null':
	return None
    try:
	# This could be less strict
        assert json_dt.startswith('new Date(Date.UTC(') and json_dt.endswith('))')
    except AssertionError:
        raise JSONDateError, 'Invalid date format'
    try:
        parts = [int(part) for part in json_dt.split('(')[2].split(')')[0].split(',')]
        num_parts = len(parts)
        if num_parts > 7:
            raise ValueError
    except (IndexError, ValueError):
        raise JSONDateError, 'Invalid date format'
    if num_parts < 3:
        raise JSONDateError, 'Not enough arguments'
    elif num_parts > 7:
        raise JSONDateError, 'Too many arguments'
    elif num_parts == 7:
        # datetime constructor will take microseconds
        parts[6] = parts[6] * 1000
    
    # An invalid date might have been specified (29 Feb in non-leap year etc.)
    try:
	# Construct as timezone-aware UTC
        return datetime.datetime(tzinfo=utc, *parts)
    except ValueError, msg:
        raise JSONDateError, 'Invalid date: %s' % msg

def test():
    import sys, traceback
    LABEL_WIDTH = 18
    print 'Naive datetime <-> JSON'
    print '(UTC is applied to naive datetimes)'
    print
    naive_now = datetime.datetime.now()
    print 'Naive now:'.ljust(LABEL_WIDTH), naive_now
    json_naive_now = datetime2JSON(naive_now)
    print 'Qooxdoo JSON UTC:'.ljust(LABEL_WIDTH), json_naive_now
    utc_now = JSON2datetime(json_naive_now)
    print 'UTC now from JSON:'.ljust(LABEL_WIDTH), utc_now
    json_now = datetime2JSON(utc_now)
    print 'Qooxdoo JSON UTC:'.ljust(LABEL_WIDTH), json_now
    assert json_naive_now == json_now
    print
    local_now = datetime.datetime.now(local_timezone)
    local_tz_name = local_timezone.tzname(local_now)
    local_tz_utcoffset = str(local_now)[-6:]
    print 'Local timezone-aware datetime <-> JSON'
    print '(converted from %s(%s) to UTC)' % (local_tz_name, local_tz_utcoffset)
    print
    print 'Local now:'.ljust(LABEL_WIDTH), local_now
    json_local_now = datetime2JSON(local_now)
    print 'Qooxdoo JSON UTC:'.ljust(LABEL_WIDTH), json_local_now
    utc_now = JSON2datetime(json_local_now)
    print 'UTC now from JSON:'.ljust(LABEL_WIDTH), utc_now
    json_now = datetime2JSON(utc_now)
    print 'Qooxdoo JSON UTC:'.ljust(LABEL_WIDTH), json_now
    assert json_local_now == json_now
    print
    print 'Various JSON -> datetime'
    def test_json(input):
	print 'Input:', repr(input)
	print 'Output:',
	try:
	    print JSON2datetime(input)
	except:
	    exc_class, exc_value, exc_tb = sys.exc_info()
	    # Don't be verbose with handled errors
	    if exc_class!=JSONDateError:
	        traceback.print_tb(exc_tb)
	    print '%s: %s' % (exc_class.__name__, exc_value)
	print '-' * 30
    test_json('')
    test_json('null')
    test_json('hum(bug(')
    test_json('new Date(Date.UTC(')
    test_json('new Date(Date.UTC(humbug))')
    test_json('new Date(Date.UTC(2006))')
    test_json('new Date(Date.UTC(2006, 6))')
    test_json('new Date(Date.UTC(2006, 6, 20))')
    test_json('new Date(Date.UTC(2006, 6, x))')
    test_json('new Date(Date.UTC(2006,6,20,13)')
    test_json('new Date(Date.UTC(2006,6,20,13))')
    test_json('new Date(Date.UTC(2006,6,31,13,55,12,123))')
    test_json('new Date( Date.UTC(2006,6,20, 13,55,12,123) )')
    test_json('new Date(Date.UTC(2006,6,20, 13,55,12,123))')

if __name__=='__main__':
    test()













_______________________________________________
qooxdoo-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel

Reply via email to