Skip to content

Commit

Permalink
many many many updates to the DateTimeCaster
Browse files Browse the repository at this point in the history
  • Loading branch information
lmajano committed Apr 2, 2024
1 parent 0f04450 commit 17c91c6
Show file tree
Hide file tree
Showing 3 changed files with 379 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
package ortus.boxlang.runtime.dynamic.casters;

import java.time.ZoneId;
import java.time.format.DateTimeParseException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

import org.apache.commons.lang3.time.DateUtils;

import ortus.boxlang.runtime.interop.DynamicObject;
import ortus.boxlang.runtime.types.DateTime;
Expand All @@ -29,6 +32,58 @@
*/
public class DateTimeCaster {

private static final String[] COMMON_PATTERNS = {

// Localized Date/Time formats
"EEE, dd MMM yyyy HH:mm:ss zzz", // Full DateTime (e.g., Tue, 02 Apr 2024 21:01:00 CEST) - Similar to FULL_FULL
"dd MMM yyyy HH:mm:ss", // Long DateTime (e.g., 02 Apr 2024 21:01:00) - Similar to LONG_LONG
"dd-MMM-yyyy HH:mm:ss", // Medium DateTime (e.g., 02-Apr-2024 21:01:00) - Might need adjustment based on locale
"dd/MM/yyyy HH:mm:ss", // Short DateTime (e.g., 02/04/2024 21:01:00) - Might need adjustment based on locale
"dd.MM.yyyy HH:mm:ss", // Short DateTime (e.g., 02.04.2024 21:01:00) - Might need adjustment based on locale

// ISO formats
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX", // Date-time with milliseconds and offset
"yyyy-MM-dd'T'HH:mm:ss.SSS", // Date-time with milliseconds
"yyyy-MM-dd'T'HH:mm:ssZ", // Date-time with offset (Z)
"yyyy-MM-dd'T'HH:mm:ssX", // Date-time with offset (X)
"yyyy-MM-dd'T'HH:mm:ss", // Date-time

// ODBC formats
"yyyyMMddHHmmss", // OBCDateTime - Potential ODBC format

// Localized Date formats
"EEE, dd MMM yyyy", // Full Date (e.g., Tue, 02 Apr 2024) - Similar to FULL
"dd MMM yyyy", // Long Date (e.g., 02 Apr 2024) - Similar to LONG
"dd-MMM-yyyy", // Medium Date (e.g., 02-Apr-2024) - Might need adjustment based on locale
"dd/MMM/yyyy", // Medium Date (e.g., 02-Apr-2024) - Might need adjustment based on locale
"dd.MMM.yyyy", // Medium Date (e.g., 02.Apr.2024) - Might need adjustment based on locale

"dd MM yyyy", // Short Date (e.g., 02.04.2024) - Might need adjustment based on locale
"dd-MM-yyyy", // Short Date (e.g., 02-04-2024) - Might need adjustment based on locale
"dd/MM/yyyy", // Short Date (e.g., 02/04/2024) - Might need adjustment based on locale
"dd.MM.yyyy", // Short Date (e.g., 02.04.2024) - Might need adjustment based on locale

// Localized Date formats - Month First
"MMM dd yyyy", // Long Date (e.g., Apr 02 2024)
"MMM-dd-yyyy", // Medium Date (e.g., Apr-02-2024) - Might need adjustment based on locale
"MMM/dd/yyyy", // Medium Date (e.g., Apr/02/2024) - Might need adjustment based on locale
"MMM.dd.yyyy", // Medium Date (e.g., Apr.02.2024) - Might need adjustment based on locale

// Localized Date formats - Month First (Short)
"MM dd yyyy", // Short Date (e.g., 04 02 2024) - Might need adjustment based on locale
"MM-dd-yyyy", // Short Date (e.g., 04-02-2024) - Might need adjustment based on locale
"MM/dd/yyyy", // Short Date (e.g., 04/02/2024) - Might need adjustment based on locale
"MM.dd.yyyy", // Short Date (e.g., 04.02.2024) - Might need adjustment based on locale

// ISO format
"yyyy-MM-dd", // ISODate (e.g., 2024-04-02)
"yyyy/MM/dd", // ISODate (e.g., 2024/04/02)
"yyyy.MM.dd", // ISODate (e.g., 2024.04.02)

// ODBC format
"yyyyMMdd" // ODBCDate - Potential ODBC format
};

/**
* Tests to see if the value can be cast.
* Returns a {@code CastAttempt<T>} which will contain the result if casting was
Expand Down Expand Up @@ -66,7 +121,9 @@ public static DateTime cast( Object object, Boolean fail ) {
}

/**
* Used to cast anything
* Used to cast anything to a DateTime object. We start off by testing the object
* against commonly known Java date objects, and then try to parse the object as a
* string. If we fail, we return null.
*
* @param object The value to cast
* @param fail True to throw exception when failing.
Expand All @@ -75,6 +132,8 @@ public static DateTime cast( Object object, Boolean fail ) {
* @return The value, or null when cannot be cast
*/
public static DateTime cast( Object object, Boolean fail, ZoneId timezone ) {

// Null is null
if ( object == null ) {
if ( fail ) {
throw new BoxCastException( "Can't cast null to a DateTime." );
Expand All @@ -83,17 +142,66 @@ public static DateTime cast( Object object, Boolean fail, ZoneId timezone ) {
}
}

// Unwrap the object
object = DynamicObject.unWrap( object );

// We have a DateTime object
if ( object instanceof DateTime targetDateTime ) {
return targetDateTime;
}

// We have a ZonedDateTime object
if ( object instanceof java.time.ZonedDateTime targetZonedDateTime ) {
return new DateTime( targetZonedDateTime );
}

// we have a Calendar object
if ( object instanceof java.util.Calendar targetCalendar ) {
return new DateTime( targetCalendar.toInstant().atZone( timezone ) );
}

// We have a LocalDateTime object
if ( object instanceof java.time.LocalDateTime targetLocalDateTime ) {
return new DateTime( targetLocalDateTime.atZone( timezone ) );
}

// We have a LocalDate object
if ( object instanceof java.time.LocalDate targetLocalDate ) {
return new DateTime( targetLocalDate.atStartOfDay( timezone ) );
}

// We have a java.util.Date object
if ( object instanceof java.util.Date targetDate ) {
return new DateTime( targetDate.toInstant().atZone( timezone ) );
}

// We have a java.sql.Date object
if ( object instanceof java.sql.Date targetDate ) {
return new DateTime( targetDate.toLocalDate().atStartOfDay( timezone ) );
}

// We have a java.sql.Timestamp object
if ( object instanceof java.sql.Timestamp targetTimestamp ) {
return new DateTime( targetTimestamp.toInstant().atZone( timezone ) );
}

// Try to cast it to a String and see if we can parse it
var targetString = StringCaster.cast( object, fail );

// Timestamp string "^\{ts ([^\}])*\}" - {ts 2023-01-01 12:00:00}
if ( targetString.matches( "^\\{ts ([^\\}]*)\\}" ) ) {
return new DateTime(
ZonedDateTime.parse( targetString, ( DateTimeFormatter ) DateTime.COMMON_FORMATTERS.get( "ODBCDateTime" ) )
);
}

// Now let's go to Apache commons lang for its date parsing
try {
return object instanceof DateTime
? ( DateTime ) object
: new DateTime( ( String ) object, timezone );
} catch ( DateTimeParseException | ClassCastException e ) {
return new DateTime( DateUtils.parseDate( targetString, COMMON_PATTERNS ) );
} catch ( java.text.ParseException e ) {
if ( fail ) {
throw e;
throw new BoxCastException( "Can't cast [" + object + "] to a DateTime.", e );
}

return null;
}

Expand Down
40 changes: 40 additions & 0 deletions src/main/java/ortus/boxlang/runtime/types/DateTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
import ortus.boxlang.runtime.types.meta.GenericMeta;
import ortus.boxlang.runtime.util.LocalizationUtil;

/**
* A DateTime object that wraps a ZonedDateTime object and provides additional functionality
* for date time manipulation and formatting the BoxLang way.
*/
public class DateTime implements IType, IReferenceable, Comparable<DateTime>, Serializable {

/**
Expand Down Expand Up @@ -83,10 +87,16 @@ public class DateTime implements IType, IReferenceable, Comparable<DateTime>, Se
public static final String ODBC_DATE_FORMAT_MASK = "'{d '''yyyy-MM-dd'''}'";
public static final String ODBC_TIME_FORMAT_MASK = "'{t '''HH:mm:ss'''}'";

/**
* Common Modes
*/
public static final String MODE_DATE = "Date";
public static final String MODE_TIME = "Time";
public static final String MODE_DATETIME = "DateTime";

/**
* Common Formatters Map so we can easily access them by name
*/
public static final IStruct COMMON_FORMATTERS = Struct.of(
"fullDateTime", DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL, FormatStyle.FULL ),
"longDateTime", DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG, FormatStyle.LONG ),
Expand Down Expand Up @@ -158,6 +168,36 @@ public DateTime( ZonedDateTime dateTime ) {
this.wrapped = dateTime;
}

/**
* Constructor to create DateTime from a java.util.Date object
* This will use the system default timezone
*
* @param date The date object
*/
public DateTime( java.util.Date date ) {
this( date.toInstant().atZone( ZoneId.systemDefault() ) );
}

/**
* Constructor to create DateTime from a LocalDateTime object
* This will use the system default timezone
*
* @param dateTime A local date time object
*/
public DateTime( LocalDateTime dateTime ) {
this( ZonedDateTime.of( dateTime, ZoneId.systemDefault() ) );
}

/**
* Constructor to create DateTime from a LocalDate object
* This will use the system default timezone
*
* @param date A local date object
*/
public DateTime( LocalDate date ) {
this( ZonedDateTime.of( date.atStartOfDay(), ZoneId.systemDefault() ) );
}

/**
* Constructor to create DateTime from a Instant
*
Expand Down
Loading

0 comments on commit 17c91c6

Please sign in to comment.