Index: trunk/FACT++/src/InterpreterV8.cc
===================================================================
--- trunk/FACT++/src/InterpreterV8.cc	(revision 18826)
+++ trunk/FACT++/src/InterpreterV8.cc	(revision 18828)
@@ -1837,8 +1837,10 @@
 {
     if (args.Length()!=2)
-        return ThrowException(String::New("dist must not be called with two arguments."));
+        return ThrowException(String::New("dist must be called with exactly two arguments."));
 
     if (!args[0]->IsObject() || !args[1]->IsObject())
-        return ThrowException(String::New("at least one argument not an object."));
+        return ThrowException(String::New("At least one argument not an object."));
+
+    // FiXME: Add a check for the argument type
 
     HandleScope handle_scope;
@@ -1908,11 +1910,85 @@
 }
 
+struct AstroArgs
+{
+    string obs;
+    Nova::LnLatPosn posn;
+    double jd;
+    uint64_t jsdate;
+
+    AstroArgs() : jsdate(0) { }
+};
+
+AstroArgs EvalAstroArgs(int offset, const Arguments &args, int8_t type=2)
+{
+    const uint8_t max = abs(type);
+
+    if (args.Length()>offset+max)
+        throw runtime_error("Number of arguments must not exceed "+to_string(offset+max)+".");
+
+    if (type==1  && args.Length()==offset+1 && !args[offset]->IsString())
+        throw runtime_error("Argument "+to_string(offset+1)+" must be a string.");
+    if (type==-1 && args.Length()==offset+1 && !args[offset]->IsDate())
+        throw runtime_error("Argument "+to_string(offset+1)+" must be a date.");
+
+    if (args.Length()==offset+1 && !(args[offset]->IsDate() || args[offset]->IsString()))
+        throw runtime_error("Argument "+to_string(offset+1)+" must be a string or Date.");
+
+    if (args.Length()==offset+2 &&
+        !(args[offset+0]->IsDate() && args[offset+1]->IsString()) &&
+        !(args[offset+1]->IsDate() && args[offset+0]->IsString()))
+        throw runtime_error("Arguments "+to_string(offset+1)+" and "+to_string(offset+2)+" must be a string/Date or Date/string.");
+
+    HandleScope handle_scope;
+
+    Local<Value> obs = args.This()->Get(String::New("observatory"));
+    if (args.Length()>offset && args[offset]->IsString())
+        obs = args[offset];
+    if (args.Length()>offset+1 && args[offset+1]->IsString())
+        obs = args[offset+1];
+
+    AstroArgs rc;
+
+    // For constructors, observatory can stay empty if not explicitly given
+    if (offset<2)
+        rc.obs = "ORM";
+
+    if (!obs.IsEmpty() && !obs->IsUndefined())
+        rc.obs = *String::AsciiValue(obs);
+
+    rc.posn = rc.obs;
+
+    if ((!rc.obs.empty() || offset==0) && !rc.posn.isValid())
+        throw runtime_error("Observatory "+rc.obs+" unknown.");
+
+    Local<Value> date = args.This()->Get(String::New("time"));
+    if (args.Length()>offset && args[offset]->IsDate())
+        date = args[offset];
+    if (args.Length()>offset+1 && args[offset+1]->IsDate())
+        date = args[offset+1];
+
+    // For constructors, time can stay empty if not explicitly given
+    if (offset<2)
+        rc.jsdate = Time().JavaDate();
+
+    if (!date.IsEmpty() && !date->IsUndefined())
+        rc.jsdate = uint64_t(date->NumberValue());
+
+    rc.jd = Time(rc.jsdate/1000, rc.jsdate%1000).JD();
+
+    return rc;
+}
+
 Handle<Value> InterpreterV8::LocalToSky(const Arguments &args)
 {
-    if (args.Length()>1)
-        return ThrowException(String::New("toSky must not be called with more than one argument."));
-
-    if (args.Length()==1 && !args[0]->IsDate())
-        return ThrowException(String::New("Argument must be a Date"));
+    AstroArgs local;
+    try
+    {
+        local = EvalAstroArgs(0, args, 2);
+    }
+    catch (const exception &e)
+    {
+        return ThrowException(String::New(e.what()));
+    }
 
     Nova::ZdAzPosn hrz;
@@ -1921,31 +1997,28 @@
 
     if (!finite(hrz.zd) || !finite(hrz.az))
-        return ThrowException(String::New("zd and az must be finite."));
+        return ThrowException(String::New("Zd and az must be finite."));
+
+    const Nova::EquPosn equ = Nova::GetEquFromHrz(hrz, local.posn, local.jd);
 
     HandleScope handle_scope;
 
-    const Local<Value> date =
-        args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
-    if (date.IsEmpty())
-        return Undefined();
-
-    const uint64_t v = uint64_t(date->NumberValue());
-    const Time utc(v/1000, v%1000);
-
-    const Nova::EquPosn equ = Nova::GetEquFromHrz(hrz, utc.JD());
-
-    // -----------------------------
-
-    Handle<Value> arg[] = { Number::New(equ.ra/15), Number::New(equ.dec), date };
-    return handle_scope.Close(fTemplateSky->GetFunction()->NewInstance(3, arg));
+    Handle<Value> arg_loc[] = { Number::New(hrz.zd), Number::New(hrz.az), String::New(local.obs.c_str()), Date::New(local.jsdate) };
+    Handle<Object> loc = fTemplateLocal->GetFunction()->NewInstance(4, arg_loc);
+
+    Handle<Value> arg_sky[] = { Number::New(equ.ra/15), Number::New(equ.dec), loc };
+    return handle_scope.Close(fTemplateSky->GetFunction()->NewInstance(3, arg_sky));
 }
 
 Handle<Value> InterpreterV8::SkyToLocal(const Arguments &args)
 {
-    if (args.Length()>1)
-        return ThrowException(String::New("toLocal must not be called with more than one argument."));
-
-    if (args.Length()==1 && !args[0]->IsDate())
-        return ThrowException(String::New("Argument must be a Date"));
+    AstroArgs local;
+    try
+    {
+        local = EvalAstroArgs(0, args, 2);
+    }
+    catch (const exception &e)
+    {
+        return ThrowException(String::New(e.what()));
+    }
 
     Nova::EquPosn equ;
@@ -1958,22 +2031,21 @@
     HandleScope handle_scope;
 
-    const Local<Value> date =
-        args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
-    if (date.IsEmpty())
-        return Undefined();
-
-    const uint64_t v = uint64_t(date->NumberValue());
-    const Time utc(v/1000, v%1000);
-
-    const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, utc.JD());
-
-    Handle<Value> arg[] = { Number::New(hrz.zd), Number::New(hrz.az), date };
-    return handle_scope.Close(fTemplateLocal->GetFunction()->NewInstance(3, arg));
+    const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, local.posn, local.jd);
+
+    Handle<Value> arg[] = { Number::New(hrz.zd), Number::New(hrz.az), String::New(local.obs.c_str()), Date::New(local.jsdate) };
+    return handle_scope.Close(fTemplateLocal->GetFunction()->NewInstance(4, arg));
 }
 
 Handle<Value> InterpreterV8::MoonToLocal(const Arguments &args)
 {
-    if (args.Length()>0)
-        return ThrowException(String::New("toLocal must not be called with arguments."));
+    AstroArgs local;
+    try
+    {
+        local = EvalAstroArgs(0, args, 1);
+    }
+    catch (const exception &e)
+    {
+        return ThrowException(String::New(e.what()));
+    }
 
     Nova::EquPosn equ;
@@ -1982,40 +2054,29 @@
 
     if (!finite(equ.ra) || !finite(equ.dec))
-        return ThrowException(String::New("ra and dec must be finite."));
+        return ThrowException(String::New("Ra and dec must be finite."));
 
     HandleScope handle_scope;
 
-    const Local<Value> date = args.This()->Get(String::New("time"));
-    if (date.IsEmpty() || date->IsUndefined() )
-        return Undefined();
-
-    const uint64_t v = uint64_t(date->NumberValue());
-    const Time utc(v/1000, v%1000);
-
-    const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, utc.JD());
-
-    Handle<Value> arg[] = { Number::New(hrz.zd), Number::New(hrz.az), date };
-    return handle_scope.Close(fTemplateLocal->GetFunction()->NewInstance(3, arg));
+    const Nova::ZdAzPosn hrz = Nova::GetHrzFromEqu(equ, local.posn, local.jd);
+
+    Handle<Value> arg[] = { Number::New(hrz.zd), Number::New(hrz.az), String::New(local.obs.c_str()), Date::New(local.jsdate) };
+    return handle_scope.Close(fTemplateLocal->GetFunction()->NewInstance(4, arg));
 }
 
 Handle<Value> InterpreterV8::ConstructorMoon(const Arguments &args)
 {
-    if (args.Length()>1)
-        return ThrowException(String::New("Moon constructor must not be called with more than one argument."));
-
-    if (args.Length()==1 && !args[0]->IsDate())
-        return ThrowException(String::New("Argument must be a Date"));
+    AstroArgs local;
+    try
+    {
+        local = EvalAstroArgs(0, args, -1);
+    }
+    catch (const exception &e)
+    {
+        return ThrowException(String::New(e.what()));
+    }
+
+    const Nova::EquPosn equ = Nova::GetLunarEquCoords(local.jd, 0.01);
 
     HandleScope handle_scope;
-
-    const Local<Value> date =
-        args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
-    if (date.IsEmpty())
-        return Undefined();
-
-    const uint64_t v = uint64_t(date->NumberValue());
-    const Time utc(v/1000, v%1000);
-
-    const Nova::EquPosn equ = Nova::GetLunarEquCoords(utc.JD(), 0.01);
 
     // ----------------------------
@@ -2033,5 +2094,5 @@
     self->Set(String::New("dec"),     Number::New(equ.dec),    ReadOnly);
     self->Set(String::New("toLocal"), function,                ReadOnly);
-    self->Set(String::New("time"),    date,                    ReadOnly);
+    self->Set(String::New("time"),    Date::New(local.jsdate), ReadOnly);
 
     return handle_scope.Close(self);
@@ -2043,6 +2104,10 @@
         return ThrowException(String::New("Sky constructor takes two or three arguments."));
 
-    if (args.Length()==3 && !args[2]->IsDate())
-        return ThrowException(String::New("Third argument must be a Date."));
+    if (args.Length()>2 && !args[2]->IsObject())
+    {
+        const string n = *String::AsciiValue(args[2]->ToObject()->GetConstructorName());
+        if (n!="Local")
+            return ThrowException(String::New("Third argument must be of type Local."));
+    }
 
     const double ra  = args[0]->NumberValue();
@@ -2050,5 +2115,5 @@
 
     if (!finite(ra) || !finite(dec))
-        return ThrowException(String::New("Both arguments to Sky must be valid numbers."));
+        return ThrowException(String::New("The first two arguments to Sky must be valid numbers."));
 
     // ----------------------------
@@ -2069,5 +2134,5 @@
     self->Set(String::New("toLocal"), function,         ReadOnly);
     if (args.Length()==3)
-        self->Set(String::New("time"), args[2], ReadOnly);
+        self->Set(String::New("local"), args[2], ReadOnly);
 
     return handle_scope.Close(self);
@@ -2076,9 +2141,13 @@
 Handle<Value> InterpreterV8::ConstructorLocal(const Arguments &args)
 {
-    if (args.Length()<2 || args.Length()>3)
-        return ThrowException(String::New("Local constructor takes two or three arguments."));
-
-    if (args.Length()==3 && !args[2]->IsDate())
-        return ThrowException(String::New("Third argument must be a Date."));
+    AstroArgs local;
+    try
+    {
+        local = EvalAstroArgs(2, args, 2);
+    }
+    catch (const exception &e)
+    {
+        return ThrowException(String::New(e.what()));
+    }
 
     const double zd = args[0]->NumberValue();
@@ -2086,5 +2155,5 @@
 
     if (!finite(zd) || !finite(az))
-        return ThrowException(String::New("Both arguments to Local must be valid numbers."));
+        return ThrowException(String::New("The first two arguments to Local must be valid numbers."));
 
     // --------------------
@@ -2104,21 +2173,21 @@
     self->Set(String::New("az"),    Number::New(az), ReadOnly);
     self->Set(String::New("toSky"), function,        ReadOnly);
-    if (args.Length()==3)
-        self->Set(String::New("time"), args[2], ReadOnly);
+    if (!local.obs.empty())
+        self->Set(String::New("observatory"), String::New(local.obs.c_str()), ReadOnly);
+    if (local.jsdate>0)
+        self->Set(String::New("time"),  Date::New(local.jsdate), ReadOnly);
 
     return handle_scope.Close(self);
 }
 
-Handle<Object> InterpreterV8::ConstructRiseSet(const Handle<Value> time, const Nova::RstTime &rst, const bool &rc)
+Handle<Object> ConstructRiseSet(const AstroArgs &args, const Nova::RstTime &rst, const bool &rc)
 {
     Handle<Object> obj = Object::New();
-    obj->Set(String::New("time"), time, ReadOnly);
-
-    const uint64_t v = uint64_t(time->NumberValue());
-    const double jd = Time(v/1000, v%1000).JD();
+    obj->Set(String::New("time"), Date::New(args.jsdate), ReadOnly);
+    obj->Set(String::New("observatory"), String::New(args.obs.c_str()), ReadOnly);
 
     const bool isUp = rc>0 ||
-        (rst.rise<rst.set && (jd>rst.rise && jd<rst.set)) ||
-        (rst.rise>rst.set && (jd<rst.set  || jd>rst.rise));
+        (rst.rise<rst.set && (args.jd>rst.rise && args.jd<rst.set)) ||
+        (rst.rise>rst.set && (args.jd<rst.set  || args.jd>rst.rise));
 
     obj->Set(String::New("isUp"), Boolean::New(rc>=0 && isUp), ReadOnly);
@@ -2142,9 +2211,13 @@
 Handle<Value> InterpreterV8::SunHorizon(const Arguments &args)
 {
-    if (args.Length()>2)
-        return ThrowException(String::New("Sun.horizon must not be called with one or two arguments."));
-
-    if (args.Length()==2 && !args[1]->IsDate())
-        return ThrowException(String::New("Second argument must be a Date"));
+    AstroArgs local;
+    try
+    {
+        local = EvalAstroArgs(1, args, 2);
+    }
+    catch (const exception &e)
+    {
+        return ThrowException(String::New(e.what()));
+    }
 
     HandleScope handle_scope;
@@ -2173,19 +2246,9 @@
 
     if (!finite(hrz))
-        return ThrowException(String::New("Second argument did not yield a valid number."));
-
-    const Local<Value> date =
-        args.Length()<2 ? Date::New(Time().JavaDate()) : args[1];
-    if (date.IsEmpty())
-        return Undefined();
-
-    const uint64_t v = uint64_t(date->NumberValue());
-    const Time utc(v/1000, v%1000);
-
-    Nova::LnLatPosn obs = Nova::kORM;
+        return ThrowException(String::New("First argument did not yield a valid number."));
 
     ln_rst_time sun;
-    const int rc = ln_get_solar_rst_horizon(utc.JD()-0.5, &obs, hrz, &sun);
-    Handle<Object> rst = ConstructRiseSet(date, sun, rc);
+    const int rc = ln_get_solar_rst_horizon(local.jd-0.5, &local.posn, hrz, &sun);
+    Handle<Object> rst = ConstructRiseSet(local, sun, rc);
     rst->Set(String::New("horizon"), Number::New(hrz));
     return handle_scope.Close(rst);
@@ -2194,25 +2257,19 @@
 Handle<Value> InterpreterV8::MoonHorizon(const Arguments &args)
 {
-    if (args.Length()>1)
-        return ThrowException(String::New("Moon.horizon must not be called with more than one argument."));
-
-    if (args.Length()==1 && !args[0]->IsDate())
-        return ThrowException(String::New("Argument must be a Date"));
+    AstroArgs local;
+    try
+    {
+        local = EvalAstroArgs(0, args, 2);
+    }
+    catch (const exception &e)
+    {
+        return ThrowException(String::New(e.what()));
+    }
 
     HandleScope handle_scope;
 
-    const Local<Value> date =
-        args.Length()==0 ? Date::New(Time().JavaDate()) : args[0];
-    if (date.IsEmpty())
-        return Undefined();
-
-    const uint64_t v = uint64_t(date->NumberValue());
-    const Time utc(v/1000, v%1000);
-
-    Nova::LnLatPosn obs = Nova::kORM;
-
     ln_rst_time moon;
-    const int rc = ln_get_lunar_rst(utc.JD()-0.5, &obs, &moon);
-    Handle<Object> rst = ConstructRiseSet(date, moon, rc);
+    const int rc = ln_get_lunar_rst(local.jd-0.5, &local.posn, &moon);
+    Handle<Object> rst = ConstructRiseSet(local, moon, rc);
     return handle_scope.Close(rst);
 };
@@ -2832,4 +2889,6 @@
     rc.emplace_back("null");
     rc.emplace_back("delete ");
+    rc.emplace_back("JSON.stringify(");
+    rc.emplace_back("JSON.parse(");
 
     rc.emplace_back("dim.log(");
Index: trunk/FACT++/src/InterpreterV8.h
===================================================================
--- trunk/FACT++/src/InterpreterV8.h	(revision 18826)
+++ trunk/FACT++/src/InterpreterV8.h	(revision 18828)
@@ -127,5 +127,4 @@
     static v8::Handle<v8::Value> MoonHorizon(const v8::Arguments &args);
     static v8::Handle<v8::Value> SunHorizon(const v8::Arguments &args);
-    static v8::Handle<v8::Object> ConstructRiseSet(const v8::Handle<v8::Value>, const ln_rst_time &, const bool &);
 #endif
 
