Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Library: Date Arithmetic

Hewee Zetkin
Registered User
Join date: 20 Jul 2006
Posts: 2,702
01-14-2009 20:40
Common date functions for (Gregorian Calendar) dates. In particular, getting the number of days between two dates, figuring out if it is a leap year, getting the number of days in a given month, etc. No range checking is done; it is assumed that dates, years, and months passed in are valid. These functions may themselves help in determining the accuracy of dates however.

Please help test this script for evetual addition to the Scripting Library. Thank you and enjoy!

CODE

//// Constants

// For non-leap-years, the number of days in (zero-bassed) month...
list MONTH_DAYS =
[ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

// For non-leap-years, the number of days from Jan 1 to the first of (zero-bassed) month...
list MONTH_OFFSETS =
[ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];

// (Zero-based)
list MONTH_ABBREVS =
[ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];

// (Zero-based)
list MONTH_NAMES =
[ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];

// (Zero-based)
list WEEKDAY_ABBREVS =
[ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ];

// (Zero-based)
list WEEKDAY_NAMES =
[ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ];


//// Functions

integer isLeapYear(integer year)
{
return (year%400 == 0) || (year%4 == 0 && year%100 != 0);
}

integer isDateInLeapYear(string date)
{
return isLeapYear((integer)llGetSubString(date, 0, 3));
}

integer leapDaysBeforeYear(integer year)
{
--year;
return llFloor(year/4)-llFloor(year/100)+llFloor(year/400);
}

integer leapDaysBeforeMonth(integer year, integer month)
{
integer leapDays = leapDaysBeforeYear(year);
if (isLeapYear(year) && month > 2)
{
return leapDays+1;
} else
{
return leapDays;
}
}

integer leapDaysBeforeDate(string date)
{
return leapDaysBeforeMonth((integer)llGetSubString(date, 0, 3), (integer)llGetSubString(date, 5, 6));
}

integer daysInMonth(integer year, integer month)
{
if (month < 1 || month > 12)
{
return 0;
} else if (month == 2 && isLeapYear(year))
{
return llList2Integer(MONTH_DAYS, 1)+1;
} else
{
return llList2Integer(MONTH_DAYS, month-1);
}
}

integer daysInMonthOfDate(string date)
{
return daysInMonth((integer)llGetSubString(date, 0, 3), (integer)llGetSubString(date, 5, 6));
}

integer daysBetween(integer year1, integer month1, integer day1,
integer year2, integer month2, integer day2)
{
integer daysInYear1 = llList2Integer(MONTH_OFFSETS, month1-1)+day1-1;
integer daysInYear2 = llList2Integer(MONTH_OFFSETS, month2-1)+day2-1;
integer leapDays1 = leapDaysBeforeMonth(year1, month1);
integer leapDays2 = leapDaysBeforeMonth(year2, month2);

return 365*(year1-year2)+(daysInYear1-daysInYear2)+(leapDays1-leapDays2);
}

integer daysBetweenDates(string date1, string date2)
{
integer year1 = (integer)llGetSubString(date1, 0, 3);
integer month1 = (integer)llGetSubString(date1, 5, 6);
integer day1 = (integer)llGetSubString(date1, 8, 9);

integer year2 = (integer)llGetSubString(date2, 0, 3);
integer month2 = (integer)llGetSubString(date2, 5, 6);
integer day2 = (integer)llGetSubString(date2, 8, 9);

return daysBetween(year1, month1, day1, year2, month2, day2);
}

integer weekdayOfDay(integer year, integer month, integer day)
{
integer daysInYear = llList2Integer(MONTH_OFFSETS, month-1)+day-1;
integer leapDays = leapDaysBeforeMonth(year, month);
return (year+daysInYear+leapDays)%7;
}

integer weekdayOfDate(string date)
{
return weekdayOfDay((integer)llGetSubString(date, 0, 3),
(integer)llGetSubString(date, 5, 6),
(integer)llGetSubString(date, 8, 9));
}


//// For Compilation

default
{
state_entry() {}
}
Nexii Malthus
[Cubitar]Mothership
Join date: 24 Apr 2006
Posts: 400
01-14-2009 21:27
Im guessing you pass it "YYYY:MM:DD" stamps.

Very nice set of functions Hewee!

Could we have something really useful like getting a date set of time in the future? Like say it was the "2009:15:01" (today) and I wanted the date that is 4 days and 1 month ahead ("0000:01:04")?

dateDifference(Current,Difference); (Difference here meant in case we wanted subtraction maybe instead of addition?)

string dateDifference("2009:15:01","+0000:01:04"); // 1 Month and 4 Days ahead.

string dateDifference("2009:15:01","-0002:01:20");// 2 Years, 1 Month and 20 days behind.

Perhaps it would be better to parse difference here, what if we wanted 60 days ahead as an example? Or 200 days (Maybe: "0:0:200")? Just for stuff like rentals, vendors and just for generally keeping time-keeping all nice and tidy :).
_____________________

Geometric Library, for all your 3D maths needs.
https://wiki.secondlife.com/wiki/Geometric

Creator of the Vertical Life Client
Hewee Zetkin
Registered User
Join date: 20 Jul 2006
Posts: 2,702
01-15-2009 00:36
Yeah, either "yyyy:mm:dd" or "yyyy-mm-dd" should work. Ah! Addition! Good point! LOL. I went one way but forgot to go the other! I'll work on that. :)
Yingzi Xue
Registered User
Join date: 11 Jun 2008
Posts: 144
01-15-2009 03:04
Thanks for these functions Hewee. Date calculations have always made my head hurt, but it seems pretty simple after looking at your script. :)
Nexii Malthus
[Cubitar]Mothership
Join date: 24 Apr 2006
Posts: 400
01-18-2009 19:13
Hewee, you should put more of your stuff on the wiki. Not just for being able to put it in a nice format but the scripting library on the forums is overloaded with many scripts that are hard to access.

Also coming myself from the teen grid since my 18th b'day, the wiki is the only accessible scripting resource for them.
_____________________

Geometric Library, for all your 3D maths needs.
https://wiki.secondlife.com/wiki/Geometric

Creator of the Vertical Life Client
Hewee Zetkin
Registered User
Join date: 20 Jul 2006
Posts: 2,702
02-08-2009 16:26
Here is an add-on library (pun only marginally intended) for adding days to date values. For example, 'daysAfterDate(4589, "1985-06-22";)' will return the date that is 4589 days after June 22, 1985. Addition of months is purposefully left off because the definition of such operations is open to interpretation (e.g. what is one month after Jan. 31, 1999? Feb. 28, 1999? Mar. 2, 1999? Mar. 3, 1999?). No week-of-year function is included either, because it is also somewhat ambiguous. For adding weeks you can just add 7 times the number in days. (Note: Adding a negative number of days works too, just in case there is question over that.)

It also allows you to calculate the date offset since the start of the last 400 year Gregorian cycle. For example 'cycleOffsetOfDate("1984-03-24";)' will give the number of days between Jan. 1, 1600 and Mar. 24, 1984, whereas 'cycleOffsetOfDate("2087-11-02";)' will give the number of days between Jan. 1, 2000 and Nov. 2, 2087.

NOTE: This code depends on the main library above, so at least some constants and functions from that must also be included.

NOTE: NOT YET COMPILED OR TESTED! I will edit this post to update the code and add fix notes as I am able to get in-world to do so.

[Fix: Realized in 'daysAfter' that I was adjusting for cycle boundaries in years when the variable is in days. Fixed.]

CODE

//// Constants

integer DAYS_PER_400_YEAR_CYCLE = 146097;


//// Functions

integer cycleOffsetOfDay(integer year, integer month, integer day)
{
year %= 400;

// Accounts for both year 0 and years 100, 200, 300.
integer dayOffsetOfYear = 365*year+(year+3)/4-(year-1)/100;

integer dayOffsetOfMonth = llList2Integer(MONTH_OFFSETS, month-1);
if (month > 2 && (year == 0 || (year%4 == 0 && year%100 != 0)))
{
++dayOffsetOfMonth;
}

return dayOffsetOfYear+dayOffsetOfMonth+(day-1);
}

integer cycleOffsetOfDate(string date)
{
cycleOffsetOfDay((integer)llGetSubString(date, 0, 3),
(integer)llGetSubString(date, 5, 6),
(integer)llGetSubString(date, 8, 9));
}

// Returns [ yearInCycle, month, day ]
list dateOffsetsOfCycleOffset(integer days)
{
// If we're over 400 years, ignore multiples of 400 years
dayLeft = days%DAYS_PER_400_YEAR_CYCLE;

// Account for the stupid year 100, 200, 300 exception (just for previous
// years for now; that is, prior to Jan 1, this year). Add one for every
// 100 years after year 1.
daysLeft += (daysLeft-366)/36524;

integer yearInCycle = 4*(daysLeft/1461);
daysLeft %= 1461;

// Account for the leap year (just for previous years for now; that is,
// prior to Jan 1, this year).
if (daysLeft >= 366)
{
yearInCycle += (daysLeft-1)/365;
daysLeft = (daysLeft-1)%365;
}

integer testDay = daysLeft;
intege daysLeft = 0;
if (yearInCycle == 0 || (yearInCycle%4 == 0 && yearInCycle%100 != 0))
{
if (daysLeft == 59)
{
testDay -= 1;
daysLeft = 1;
} else if (daysLeft > 59)
{
testDay -= 1;
}
}

integer month = 0;
integer monthDays = llList2Integer(MONTH_DAYS, month);
while (month < 12 && testDay > monthDays)
{
testDay -= monthDays;

++month;
monthDays = llList2Integer(MONTH_DAYS, month);
}

++month;
integer day = testDay+daysLeft+1;

return [ yearInCycle, month, day ];
}

// Returns [ year, month, day ]
list daysAfter(integer dayDiff, integer year, integer month, integer day)
{
integer cycle = year/400;
year %= 400;

integer cycleOffset = cycleOffsetOfDay(year, month, day);
cycleOffset += dayDiff;
if (cycleOffset < 0)
{
// Stupid non-mathematical % behavior for negative dividends....
integer cycleDiff =
(DAYS_PER_400_YEAR_CYCLE-1-cycleOffset)/DAYS_PER_400_YEAR_CYCLE;
cycle -= cycleDiff;
cycleOffset += cycleDiff*DAYS_PER_400_YEAR_CYCLE;
} else
{
cycle += cycleOffset/DAYS_PER_400_YEAR_CYCLE;
cycleOffset %= DAYS_PER_400_YEAR_CYCLE;
}

list offsets = dateOffsetsOfCycleOffset(cycleOffset);
year = 400*cycle+llList2Integer(offsets, 0);
month = llList2Integer(offsets, 1);
day = llList2Integer(offsets, 2);

return [ year, month, day ];
}

string daysAfterDate(integer dayDiff, string date)
{
list dateVals =
daysAfter(
dayDiff,
(integer)llGetSubString(date, 0, 3),
(integer)llGetSubString(date, 5, 6),
(integer)llGetSubString(date, 8, 9));
integer year = llList2Integer(dateVals, 0);
integer month = llList2Integer(dateVals, 1);
integer day = llList2Integer(dateVals, 2);

string yearStr;
if (year < 10)
{
yearStr = "000"+(string)year;
} else if (year < 100)
{
yearStr = "00"+(string)year;
} else if (year < 1000)
{
yearStr = "0"+(string)year;
} else
{
yearStr = (string)year;
}

string monthStr;
if (month < 10)
{
monthStr = "0"+(string)month;
} else
{
monthStr = (string)month;
}

integer dayStr;
if (day < 10)
{
dayStr = "0"+(string)day;
} else
{
dayStr = (string)day;
}

return yearStr+"-"+monthStr+"-"+dayStr;
}