/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.lang;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.chrono.ISOChronology;
import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationContext;
import divconq.schema.CoreType;
import divconq.schema.DataType;
import divconq.util.StringUtil;
/**
* BigDateTime can handle dates into the billions of years, both past and future. It is used to store dates
* in the database (dcDb) and for sorting. It is not (yet) useful for date manipulation, for most
* common date manipulation use the Joda (provided with DivConq) instead.
*
* This is designed to be a versatile way to handle historical data. Dates are based off proleptic Gregorian,
* time is optional but if present then is based off UTC zone. If only date is present then it is assumed
* to be incomplete and not zone based.
*
* Only year is required. Year may be 0 - 99,999,999,999. However, DivConq treats year 50,000,000,001
* as 1 CE (aka AD). Therefore if you wish to refer to a Common Era (AD) date then add 50 billion to the
* year. If you wish to refer to a BCE (aka BC) date then subtract that year from 50,000,000,001. So
* 1 BCE is 50,000,000,000. Year is 11 digits, 0 padded.
*
* Month is optional, if missing then it is understood that the date is for "sometime this year".
* Month is in traditional 01 - 12 format.
*
* Day is optional, if missing but month is present then the date is for "sometime this month".
* Day is in traditional 01 - 31 format.
*
* Hour is optional, if missing but day is present then the date is for "sometime this day"
* and is not linked to a timezone. If an hour is present it must be in UTC time.
* Hour is in traditional 00 - 23 format.
*
* Minute is optional, if missing but hour is present then the date is for "sometime this hour"
* and is linked to the UTC timezone. If an minute is present it must be in UTC time.
* Minute is in traditional 00 - 59 format.
*
* Second is optional, if missing but minute is present then the date is for "sometime this minute"
* and is linked to the UTC timezone.
* Second is in traditional 00 - 59 format.
*
* Internal format of datetime is "tYYYYYYYYYYYMMDDhhmmss". For example, Sept 2004 is
* "t5000000200409".
*
* BigDateTime does not carry any information about which TimeZone it was translated from
* for which Chronology (Calendar) it was converted from. Always use strict UTC and
* proleptic Gregorian conversions so that all dates collate/index correctly relative
* to each other.
*
*/
public class BigDateTime implements Comparable<BigDateTime> {
protected Long year = null;
protected Integer month = null;
protected Integer day = null;
protected Integer hour = null;
protected Integer minute = null;
protected Integer second = null;
public Long getYear() {
return this.year;
}
public Integer getMonth() {
return this.month;
}
public Integer getDay() {
return this.day;
}
public Integer getHour() {
return this.hour;
}
public Integer getMinute() {
return this.minute;
}
public Integer getSecond() {
return this.second;
}
/**
* Creates an empty, and invalid, datetime.
*/
public BigDateTime() {
this(new DateTime());
}
/**
* @param date translates into BigDateTime
*/
public BigDateTime(DateTime date) {
if (date == null)
return;
// make sure we are using ISO and UTC
date = date.toDateTime(ISOChronology.getInstanceUTC());
//date = date.toDateTime(DateTimeZone.UTC);
this.year = 50000000000L + date.getYear(); // ISO says 1 BCE = 0, 2 BCE = -1, etc
this.month = date.getMonthOfYear();
this.day = date.getDayOfMonth();
this.hour = date.getHourOfDay();
this.minute = date.getMinuteOfHour();
this.second = date.getSecondOfMinute();
}
/**
* @param date translates into BigDateTime, assumes ISOChronology
*/
public BigDateTime(LocalDate date) {
if (date == null)
return;
this.year = 50000000000L + date.getYear(); // ISO says 1 BCE = 0, 2 BCE = -1, etc
this.month = date.getMonthOfYear();
this.day = date.getDayOfMonth();
}
/**
* @param year where 1 = 1 CE (AD), 0 = 1 BCE (BC)
*/
public BigDateTime(long year) {
if ((year < -50000000000L) || (year > 49999999999L))
return;
this.year = 50000000000L + year; // ISO says 1 BCE = 0, 2 BCE = -1, etc
}
/**
* @param year where 1 = 1 CE (AD), 0 = 1 BCE (BC)
* @param month where 1 = Jan
*/
public BigDateTime(long year, int month) {
this(year);
if ((month < 1) || (month > 12))
return;
this.month = month;
}
/**
* @param year where 1 = 1 CE (AD), 0 = 1 BCE (BC)
* @param month where 1 = Jan
* @param day of month
*/
public BigDateTime(long year, int month, int day) {
this(year, month);
if ((day < 1) || (day > 31)) // not 100% accurate, but ballpark
return;
this.day = day;
}
/**
* @param year where 1 = 1 CE (AD), 0 = 1 BCE (BC)
* @param month where 1 = Jan
* @param day of month
* @param hour of day
*/
public BigDateTime(long year, int month, int day, int hour) {
this(year, month, day);
if ((hour < 0) || (hour > 23))
return;
this.hour = hour;
}
/**
* @param year where 1 = 1 CE (AD), 0 = 1 BCE (BC)
* @param month where 1 = Jan
* @param day of month
* @param hour of day
* @param minute of hour
*/
public BigDateTime(long year, int month, int day, int hour, int minute) {
this(year, month, day, hour);
if ((minute < 0) || (minute > 59))
return;
this.minute = minute;
}
/**
* @param year where 1 = 1 CE (AD), 0 = 1 BCE (BC)
* @param month where 1 = Jan
* @param day of month
* @param hour of day
* @param minute of hour
* @param second of minute
*/
public BigDateTime(long year, int month, int day, int hour, int minute, int second) {
this(year, month, day, hour, minute);
if ((second < 0) || (second > 59))
return;
this.second = second;
}
/**
* @return in internal format
*/
@Override
public String toString() {
if (this.year == null)
return null;
String res = "t" + StringUtil.leftPad(this.year + "", 11, '0');
if (this.month != null) {
res += StringUtil.leftPad(this.month + "", 2, '0');
if (this.day != null) {
res += StringUtil.leftPad(this.day + "", 2, '0');
if (this.hour != null) {
res += StringUtil.leftPad(this.hour + "", 2, '0');
if (this.minute != null) {
res += StringUtil.leftPad(this.minute + "", 2, '0');
if (this.second != null)
res += StringUtil.leftPad(this.second + "", 2, '0');
}
}
}
}
return res;
}
static public BigDateTime nowDateTime() {
return new BigDateTime(new DateTime());
}
static public BigDateTime nowDate() {
return new BigDateTime(new LocalDate());
}
static public BigDateTime parseOrNull(String date) {
FuncResult<BigDateTime> r = BigDateTime.parse(date);
return r.getResult();
}
/**
* @param date internal big datetime format
* @return BigDateTime object plus warnings, if any
*/
static public FuncResult<BigDateTime> parse(String date) {
FuncResult<BigDateTime> fr = new FuncResult<BigDateTime>();
if (StringUtil.isEmpty(date)) {
fr.errorTr(230);
return fr;
}
DataType dt = OperationContext.get().getSchema().getType("BigDateTime");
if (dt == null) {
fr.errorTr(232);
return fr;
}
CoreType ct = dt.getCoreType();
if (ct == null) {
fr.errorTr(232);
return fr;
}
if (!ct.validateData(date)) {
fr.errorTr(231, date);
return fr;
}
BigDateTime bd = new BigDateTime();
fr.setResult(bd);
bd.year = Long.parseLong(date.substring(1, 12));
if (date.length() > 12)
bd.month = Integer.parseInt(date.substring(12, 14));
if (date.length() > 14)
bd.day = Integer.parseInt(date.substring(14, 16));
if (date.length() > 16)
bd.hour = Integer.parseInt(date.substring(16, 18));
if (date.length() > 18)
bd.minute = Integer.parseInt(date.substring(18, 20));
if (date.length() > 20)
bd.second = Integer.parseInt(date.substring(20, 22));
return fr;
}
@Override
public int compareTo(BigDateTime o) {
if (o == null)
return 1;
if (this.year < o.year)
return -1;
if (this.year > o.year)
return 1;
if (this.month < o.month)
return -1;
if (this.month > o.month)
return 1;
if (this.day < o.day)
return -1;
if (this.day > o.day)
return 1;
if (this.hour < o.hour)
return -1;
if (this.hour > o.hour)
return 1;
if (this.minute < o.minute)
return -1;
if (this.minute > o.minute)
return 1;
if (this.second < o.second)
return -1;
if (this.second > o.second)
return 1;
return 0;
}
}