Skip to content

Commit

Permalink
Merge branch 'development' of github.com:ortus-solutions-private/boxl…
Browse files Browse the repository at this point in the history
…ang into development
  • Loading branch information
grantcopley committed Jan 14, 2024
2 parents 352a6ed + 4e300fc commit 4071916
Show file tree
Hide file tree
Showing 6 changed files with 790 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/main/java/ortus/boxlang/runtime/BoxRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ private void startup() {
this.logger.atInfo().log(
"+ BoxLang Runtime Started at [{}] in [{}]",
Instant.now(),
timerUtil.stop( "runtime-startup" )
timerUtil.start( "runtime-startup" )
);

// Announce it baby! Runtime is up
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

package ortus.boxlang.runtime.bifs.global.temporal;

import java.time.ZoneId;
import java.util.HashMap;

import ortus.boxlang.runtime.bifs.BIF;
import ortus.boxlang.runtime.bifs.BoxBIF;
import ortus.boxlang.runtime.bifs.BoxMember;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.dynamic.casters.DateTimeCaster;
import ortus.boxlang.runtime.scopes.ArgumentsScope;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.Argument;
import ortus.boxlang.runtime.types.BoxLangType;
import ortus.boxlang.runtime.types.DateTime;
import ortus.boxlang.runtime.types.Struct;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;

@BoxBIF( alias = "Year" )
@BoxBIF( alias = "Month" )
@BoxBIF( alias = "Day" )
@BoxBIF( alias = "DayOfWeek" )
@BoxBIF( alias = "DayOfWeekAsString" )
@BoxBIF( alias = "DayOfWeekShortAsString" )
@BoxBIF( alias = "DayOfYear" )
@BoxBIF( alias = "Hour" )
@BoxBIF( alias = "Minute" )
@BoxBIF( alias = "Second" )
@BoxBIF( alias = "Millisecond" )
@BoxBIF( alias = "Nanosecond" )
@BoxBIF( alias = "Offset" )
@BoxBIF( alias = "GetTimezone" )
@BoxMember( type = BoxLangType.DATETIME, name = "year" )
@BoxMember( type = BoxLangType.DATETIME, name = "month" )
@BoxMember( type = BoxLangType.DATETIME, name = "day" )
@BoxMember( type = BoxLangType.DATETIME, name = "dayOfWeek" )
@BoxMember( type = BoxLangType.DATETIME, name = "dayOfWeekAsString" )
@BoxMember( type = BoxLangType.DATETIME, name = "dayOfWeekShortAsString" )
@BoxMember( type = BoxLangType.DATETIME, name = "dayOfYear" )
@BoxMember( type = BoxLangType.DATETIME, name = "hour" )
@BoxMember( type = BoxLangType.DATETIME, name = "minute" )
@BoxMember( type = BoxLangType.DATETIME, name = "second" )
@BoxMember( type = BoxLangType.DATETIME, name = "millisecond" )
@BoxMember( type = BoxLangType.DATETIME, name = "nanosecond" )
@BoxMember( type = BoxLangType.DATETIME, name = "offset" )
@BoxMember( type = BoxLangType.DATETIME, name = "timezone" )

public class TimeUnits extends BIF {

/**
* Map of method names to BIF names
*/
public final static Struct methodMap = new Struct(
new HashMap<String, String>() {

{
put( "Year", "getYear" );
put( "Day", "getDayOfMonth" );
put( "DayOfYear", "getDayOfYear" );
put( "Hour", "getHour" );
put( "Minute", "getMinute" );
put( "Second", "getSecond" );
put( "Nanosecond", "getNano" );
}
}
);

/**
* Constructor
*/
public TimeUnits() {
super();
declaredArguments = new Argument[] {
new Argument( true, "any", Key.date ),
new Argument( false, "timezone", Key.timezone )
};
}

/**
* Provides the BIF and member functions for all time unit request with no arguments
*
* @param context The context in which the BIF is being invoked.
* @param arguments Argument scope for the BIF.
*
* @argument.date The DateTime object or datetime string representation
*
* @argument.timezone The timezone with which to cast the result
*/
public Object invoke( IBoxContext context, ArgumentsScope arguments ) {
DateTime dateRef = DateTimeCaster.cast( arguments.get( Key.date ) );

if ( arguments.get( Key.timezone ) != null ) {
dateRef = dateRef.clone( ZoneId.of( arguments.getAsString( Key.timezone ) ) );
}

Key bifMethodKey = arguments.getAsKey( __functionName );
String methodName = null;
if ( methodMap.containsKey( bifMethodKey ) ) {
methodName = ( String ) methodMap.get( ( Object ) bifMethodKey );
return dateRef.dereferenceAndInvoke( context, Key.of( methodName ), arguments, false );
} else {
switch ( bifMethodKey.getName().toLowerCase() ) {
case "month" : {
return dateRef.getWrapped().getMonth().getValue();
}
case "dayofweek" : {
return dateRef.clone().getWrapped().getDayOfWeek().getValue();
}
case "dayofweekasstring" : {
return dateRef.clone().format( "eeee" );
}
case "dayofweekshortasstring" : {
return dateRef.clone().format( "eee" );
}
case "millisecond" : {
return dateRef.getWrapped().getNano() / 1000000;
}
case "offset" : {
return dateRef.clone().format( "xxxx" );
}
case "gettimezone" :
case "timezone" : {
return dateRef.clone().format( "v" );
}
default : {
throw new BoxRuntimeException(
String.format(
"The method [%s] is not present in the [%s] object",
arguments.getAsString( Key.of( __functionName ) ),
dateRef.getClass().getSimpleName()
)
);
}

}

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
import ortus.boxlang.runtime.scopes.IntKey;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.Array;
import ortus.boxlang.runtime.types.IType;
import ortus.boxlang.runtime.types.IStruct;
import ortus.boxlang.runtime.types.IType;
import ortus.boxlang.runtime.types.exceptions.ApplicationException;
import ortus.boxlang.runtime.types.exceptions.BoxLangException;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;
Expand Down Expand Up @@ -298,10 +298,10 @@ public static <T> T invokeConstructor( IBoxContext context, Class<T> targetClass

/**
* Reusable method for bootstrapping IClassRunnables
*
*
* @param cfc The class to bootstrap
* @param args The arguments to pass to the constructor
*
*
* @return The instance of the class
*/
private static <T> T bootstrapBLClass( IBoxContext context, IClassRunnable cfc, Object[] positionalArgs, Map<Key, Object> namedArgs, boolean noInit ) {
Expand Down Expand Up @@ -460,7 +460,7 @@ public static Object invoke( Class<?> targetClass, Object targetInstance, String
if ( safe ) {
return null;
} else {
throw new BoxRuntimeException( "Error getting method for class " + targetClass.getName(), e );
throw new BoxRuntimeException( "Error getting method " + methodName + " for class " + targetClass.getName(), e );
}
}

Expand Down
152 changes: 144 additions & 8 deletions src/main/java/ortus/boxlang/runtime/types/DateTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;

import ortus.boxlang.runtime.BoxRuntime;
import ortus.boxlang.runtime.bifs.MemberDescriptor;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.dynamic.IReferenceable;
import ortus.boxlang.runtime.interop.DynamicJavaInteropService;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.services.FunctionService;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;
import ortus.boxlang.runtime.types.meta.BoxMeta;
import ortus.boxlang.runtime.types.meta.GenericMeta;

public class DateTime implements IType {
public class DateTime implements IType, IReferenceable {

/**
* --------------------------------------------------------------------------
Expand All @@ -42,13 +51,18 @@ public class DateTime implements IType {
/**
* Represents the wrapped ZonedDateTime object we enhance
*/
protected ZonedDateTime wrapped;
protected ZonedDateTime wrapped;

/**
* The format we use to represent the date time
* which defaults to the ODBC format: {ts '''yyyy-MM-dd HH:mm:ss'''}
*/
private DateTimeFormatter formatter = DateTimeFormatter.ofPattern( ODBC_FORMAT_MASK );
private DateTimeFormatter formatter = DateTimeFormatter.ofPattern( ODBC_FORMAT_MASK );

/**
* Function service
*/
private static final FunctionService functionService = BoxRuntime.getInstance().getFunctionService();

/**
* --------------------------------------------------------------------------
Expand All @@ -59,15 +73,15 @@ public class DateTime implements IType {
/**
* Formatters
*/
public static final String ODBC_FORMAT_MASK = "'{ts '''yyyy-MM-dd HH:mm:ss'''}'";
public static final String DEFAULT_DATE_FORMAT_MASK = "dd-MMM-yy";
public static final String DEFAULT_TIME_FORMAT_MASK = "HH:mm a";
public static final String DEFAULT_DATETIME_FORMAT_MASK = "dd-MMM-yyyy HH:mm:ss";
public static final String ODBC_FORMAT_MASK = "'{ts '''yyyy-MM-dd HH:mm:ss'''}'";
public static final String DEFAULT_DATE_FORMAT_MASK = "dd-MMM-yy";
public static final String DEFAULT_TIME_FORMAT_MASK = "HH:mm a";
public static final String DEFAULT_DATETIME_FORMAT_MASK = "dd-MMM-yyyy HH:mm:ss";

/**
* Metadata object
*/
public BoxMeta $bx;
public BoxMeta $bx;

/**
* --------------------------------------------------------------------------
Expand Down Expand Up @@ -330,6 +344,22 @@ public String toString() {
return this.formatter.format( this.wrapped );
}

/*
* Clones this object to produce a new object
*/
public DateTime clone() {
return clone( this.wrapped.getZone() );
}

/**
* Clones this object to produce a new object
*
* @param timezone the string timezone to cast the clone to
*/
public DateTime clone( ZoneId timezone ) {
return new DateTime( ZonedDateTime.ofInstant( this.wrapped.toInstant(), timezone ) );
}

/**
* Returns the date time representation as a string in the specified format mask
*
Expand Down Expand Up @@ -456,4 +486,110 @@ public ZonedDateTime getWrapped() {
return this.wrapped;
}

/**
* --------------------------------------------------------------------------
* IReferenceable Interface Methods
* --------------------------------------------------------------------------
*/

/**
* Assign a value to a key
*
* @param key The key to assign
* @param value The value to assign
*/
@Override
public Object assign( IBoxContext context, Key key, Object value ) {
DynamicJavaInteropService.setField( this, key.getName().toLowerCase(), value );
return this;
}

/**
* Dereference this object by a key and return the value, or throw exception
*
* @param key The key to dereference
* @param safe Whether to throw an exception if the key is not found
*
* @return The requested object
*/
@Override
public Object dereference( IBoxContext context, Key key, Boolean safe ) {
try {
return DynamicJavaInteropService.getField( this, key.getName().toLowerCase() ).get();
} catch ( NoSuchElementException e ) {
throw new BoxRuntimeException(
String.format(
"The property [%s] does not exist or is not public in the class [%s].",
key.getName(),
this.getClass().getSimpleName()
)
);
}
}

/**
* Dereference this object by a key and invoke the result as an invokable (UDF, java method) using positional arguments
*
* @param name The key to dereference
* @param positionalArguments The positional arguments to pass to the invokable
* @param safe Whether to throw an exception if the key is not found
*
* @return The requested object
*/
public Object dereferenceAndInvoke( IBoxContext context, Key name, Object[] positionalArguments, Boolean safe ) {
MemberDescriptor memberDescriptor = functionService.getMemberMethod( name, BoxLangType.DATETIME );
if ( memberDescriptor != null ) {
return memberDescriptor.invoke( context, this, positionalArguments );
}

if ( DynamicJavaInteropService.hasMethodNoCase( this.getClass(), name.getName() ) ) {
return DynamicJavaInteropService.invoke( this, name.getName(), safe, positionalArguments );
} else if ( DynamicJavaInteropService.hasMethodNoCase( this.wrapped.getClass(), name.getName() ) ) {
return DynamicJavaInteropService.invoke( this.wrapped, name.getName(), safe, positionalArguments );
} else if ( DynamicJavaInteropService.hasMethodNoCase( this.getClass(), "get" + name.getName() ) ) {
return DynamicJavaInteropService.invoke( this.wrapped, "get" + name.getName(), safe, positionalArguments );
} else {
throw new BoxRuntimeException(
String.format(
"The method [%s] is not present in the [%s] object",
name.getName(),
this.getClass().getSimpleName()
)
);
}
}

/**
* Dereference this object by a key and invoke the result as an invokable (UDF, java method)
*
* @param name The name of the key to dereference, which becomes the method name
* @param namedArguments The arguments to pass to the invokable
* @param safe If true, return null if the method is not found, otherwise throw an exception
*
* @return The requested return value or null
*/
public Object dereferenceAndInvoke( IBoxContext context, Key name, Map<Key, Object> namedArguments, Boolean safe ) {

MemberDescriptor memberDescriptor = functionService.getMemberMethod( name, BoxLangType.DATETIME );
if ( memberDescriptor != null ) {
return memberDescriptor.invoke( context, this, namedArguments );
}
if ( DynamicJavaInteropService.hasMethodNoCase( this.getClass(), name.getName() ) ) {
return DynamicJavaInteropService.invoke( this, name.getName(), safe, namedArguments );
// no args - just pass through to the wrapped methods
} else if ( DynamicJavaInteropService.hasMethodNoCase( this.wrapped.getClass(), name.getName() ) ) {
return DynamicJavaInteropService.invoke( this.wrapped, name.getName(), safe );
} else if ( DynamicJavaInteropService.hasMethodNoCase( this.getClass(), "get" + name.getName() ) ) {
return DynamicJavaInteropService.invoke( this.wrapped, "get" + name.getName(), safe );
} else {
throw new BoxRuntimeException(
String.format(
"The method [%s] is not present in the [%s] object",
name.getName(),
this.getClass().getSimpleName()
)
);
}
}

}
Loading

0 comments on commit 4071916

Please sign in to comment.