Calendar component

When I run the demo, it crashes (Windows XP)

That’s a very useful comment.

Sorry for the demo crashes. I know this issue. That’s because of GCC CPU optimizations (SSE2, SSE3 and so on) I’ve turned on the Release compilation. These optimizations are not compatible for all CPU types. Just recompile the demo applications yourself. Sorry :wink:

[quote=ā€œPtomaineā€][quote=ā€œkrakenā€]yeah very cool indeed. something i had in mind to do for months: clean and simple to use.
anyway i’ll try to clean up the code and convert it to be pure 100% juce code compliant (with comments also :P) without any other include needed, if is not a problem…[/quote]

I’m sure you’ll do it right.
Just for you to note:

The juce::Time class has limits for the year value the class is able to work with (against Poco’s from 0 to 9999). The limits are from 1970 to 2037. In my component, the year of value 0 has a special meaning for special dates and date periods. It indicates that the special date or period just last forever (for every single year). Please, keep it in your mind. Also, the reason for having CalendarComponentSettings class is you can create different localized settings and share them between different components (reuse them) instead of resetting the same settings for each component each time they are created. Just supply settings for components and they tunes itself for it, even runtime. The component refers to a settings instance. About runtime, component is not aware when you change its settings. Then do not forget to call redrawCalendar member function to make all components that refer your settings instance reflect your new settings.

Good luck! I believe in you, kraken :wink:[/quote]

Hi all!

I hope it is okay, that I couldn’t wait any longer. :oops: I really loved the beauty of the component and wanted to try it so I had to do the modifications myself.

The resulting version can be found at CalendarComponentJuceTime.7z

I didn’t change the way it works, just ported it to juce::Time.

I also thought it would be useful to use the component for a wider range of dates, so I propose this patch to have a new juce_Config.h option named JUCE_EXTENDEDTIME that enables additional date/time calculations to enhance the range with a minimal change of juce_Time.cpp:

Index: juce_Config.h
===================================================================
--- juce_Config.h	(revision 255)
+++ juce_Config.h	(working copy)
@@ -175,5 +175,17 @@
 #endif
 
 //=============================================================================
+/** Enable this to get extended Time range.
 
+    Turning this on gives you a correct date handling for dates after year 1528
+    to dates far beyond relevance.
+    Attention: Daylight saving time handling is expected to be correct only for
+    time range 1970 to 2038.
+*/
+#ifndef JUCE_EXTENDEDTIME
+  #define JUCE_EXTENDEDTIME 1
 #endif
+
+//=============================================================================
+
+#endif
Index: src/juce_core/basics/juce_Time.cpp
===================================================================
--- src/juce_core/basics/juce_Time.cpp	(revision 255)
+++ src/juce_core/basics/juce_Time.cpp	(working copy)
@@ -60,11 +60,49 @@
   #endif
 #endif
 
+// Save variant of operator% for the time calculations with negative time
+inline int timeModulo (int64 t, int f)
+{
+    return t >= 0 ? (int)(t % f) : (int)(t - (t/f+1)*f);
+}
+
 //==============================================================================
+static const int64 jdSecondsOfEpoch = 210866803200LL;
+
 static void millisToLocal (const int64 millis, struct tm& result) throw()
 {
-    time_t now = (time_t) (millis / 1000);
+    int64 seconds = millis / 1000;
 
+#ifdef JUCE_EXTENDEDTIME
+    if (seconds < 86400 || seconds >= 2145916800L)
+    {
+        // outside the save range 1970-01-02T00:00:00 to 2038-01-01T00:00:00
+        // math based on calendar faq http://www.tondering.dk/claus/calendar.html
+        static int timeZone = 86400 - (int)Time(1970, 0, 2, 0, 0).toMilliseconds()/1000;
+        int64 jdm = seconds + jdSecondsOfEpoch + timeZone;
+        int a = int(jdm / 86400L) + 32044;
+        int b = (4*a+3)/146097;
+        int c = a - (b*146097)/4;
+        int d = (4*c+3)/1461;
+        int e = c - (1461*d)/4;
+        int m = (5*e+2)/153;
+
+        result.tm_mday = e - (153*m+2)/5 + 1;
+        result.tm_mon  = m + 2 - 12*(m/10);
+        result.tm_year = b*100 + d - 6700 + m/10;
+        result.tm_wday = int(jdm / 86400L + 1) % 7;
+        result.tm_yday = -1;
+        int t = int(jdm % 86400L);
+        result.tm_hour = t / 3600; t = t % 3600;
+        result.tm_min  = t / 60;
+        result.tm_sec  = t % 60;
+        result.tm_isdst = -1;
+        return;
+    }
+#endif
+
+    time_t now = (time_t) (seconds);
+    
 #if JUCE_WIN32
   #ifdef USE_NEW_SECURE_TIME_FNS
     if (now >= 0 && now <= 0x793406fff)
@@ -80,6 +118,27 @@
 #endif
 }
 
+static void localToMillis (struct tm& local, int64& millis)
+{
+#ifdef JUCE_EXTENDEDTIME
+    if (local.tm_year < 71 || local.tm_year >= 138)
+    {
+        // outside the save range 1971-01-01T00:00:00 to 2038-01-01T00:00:00
+        // math based on calendar faq http://www.tondering.dk/claus/calendar.html
+        static int timeZone = 86400 - (int)Time(1970, 0, 2, 0, 0).toMilliseconds()/1000;
+        int a = (13 - local.tm_mon) / 12;
+        int y = local.tm_year + 6700 - a;
+        int m = local.tm_mon + 12*a - 2;
+        int jd = local.tm_mday + (153*m+2)/5 + y*365 + y/4 - y/100 + y/400 - 32045;
+        int64 s = (int64)(jd) * 86400L - jdSecondsOfEpoch;
+        millis = (s + local.tm_hour * 3600 + local.tm_min * 60 + local.tm_sec - timeZone) * 1000;
+        return;
+    }
+#endif
+    millis = 1000 * (int64) mktime (&local);
+    if (millis < 0)
+        millis = 0;
+}
 
 //==============================================================================
 Time::Time() throw()
@@ -117,11 +176,9 @@
     t.tm_sec    = seconds;
     t.tm_isdst  = -1;
 
-    millisSinceEpoch = 1000 * (int64) mktime (&t);
+    localToMillis(t, millisSinceEpoch);
 
-    if (millisSinceEpoch < 0)
-        millisSinceEpoch = 0;
-    else
+    if( millisSinceEpoch )
         millisSinceEpoch += milliseconds;
 }
 
@@ -370,17 +427,17 @@
 
 int Time::getMinutes() const throw()
 {
-    return (int) ((millisSinceEpoch / 60000) % 60);
+    return timeModulo(millisSinceEpoch / 60000, 60);
 }
 
 int Time::getSeconds() const throw()
 {
-    return (int) ((millisSinceEpoch / 1000) % 60);
+    return timeModulo(millisSinceEpoch / 1000, 60);
 }
 
 int Time::getMilliseconds() const throw()
 {
-    return (int) (millisSinceEpoch % 1000);
+    return timeModulo(millisSinceEpoch, 1000);
 }
 
 bool Time::isDaylightSavingTime() const throw()

This changes allow negative milliseconds in Time when the option is enabled and handle the dates outside the save range with math from the calendar faq.
It is importand to know that this calculations do not handle daylight saving times for dates before 1970 and after 2037, but for years before 1970 the usage of DST was quite irregular and nobody knows if there really will be DST after 2037 as even now there are discussions about the usefulness. Also dates outside this range are normally only used for calendar calculations or storage, so DST handling is not really important there.

When activating this option Time is able to easily handle a range from 1600 to 3000. The modifications of the CalendarComponent detect the activated option and allow that range. If you don’t use the Time extension, it limits selection from 1971 to 2037. (As already said are dates before 1582 different and even Poco handles them wrong, so I see a practical limit in 1600.)

@Ptomaine: If you don’t want this derivate to exist, please let me know and I remove the download.

@jules: If you want a different (or no) approach (a derived TimeEx class or something like that), let me know.

@all: If there is something wrong or you have a question, please don’t hesitate to ask. :wink:

That’s very interesting! If your millisToLocal and localToMillis functions were tweaked to include DST in their calculations, would they be good enough to replace calls to localtime() or mktime() altogether?

Ah no, I just noticed that you’re still using the built-in version for getting the timezone and stuff. Pity it’s so complicated to calculate, as I’d love to just replace the library calls with some inline code.

Still, I like what you’ve done, and don’t see any reason not to merge it in - thanks!

nice to hear ! hey jules, what about including a clean version of this component inside the juce tree ? (i’ll never stop asking about this, sorry !)

ah a nice tweak could be to replace T macros with TRANS, to permit localization of Calendar strings…

Yep, I should tart it up and add it to the tree - I’ll get onto it as soon as I can!

Ok, I’ve just checked in my version of gulrak’s maths improvements - if you guys want to have a go and see if it works, that’d be appreciated!

Well… we both did not flawless. :wink:

One last minute bug fix from me (changing the lower border to 1971 instead ot 1970, to not risk faults when time zone correction puts a date below 1970 on 1/1/1970) leads to a recursion with stack overflow when calculating the time zone info. :oops:

And some bugs crept in during your modifications of my math (a 32 bit arithmetic overflow and 1900 years to much due to tm_year in my function was without century).

The patch:

Index: juce_Time.cpp
===================================================================
--- juce_Time.cpp	(revision 262)
+++ juce_Time.cpp	(working copy)
@@ -68,7 +68,7 @@
     if (seconds < literal64bit (86400) || seconds >= literal64bit (2145916800))
     {
         // use extended maths for dates beyond 1970 to 2037..
-        const int timeZoneAdjustment = 86400 - (int) (Time (1970, 0, 2, 0, 0).toMilliseconds() / 1000);
+        const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000);
         const int64 jdm = seconds + timeZoneAdjustment + literal64bit (210866803200);
 
         const int days = (int) (jdm / literal64bit (86400));
@@ -141,14 +141,14 @@
     if (year < 1971 || year >= 2038)
     {
         // use extended maths for dates beyond 1970 to 2037..
-        const int timeZoneAdjustment = 86400 - (int) (Time (1970, 0, 2, 0, 0).toMilliseconds() / 1000);
+        const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000);
         const int a = (13 - month) / 12;
-        const int y = year + 6700 - a;
+        const int y = year + 4800 - a;
         const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 
                            + (y * 365) + (y /  4) - (y / 100) + (y / 400)
                            - 32045;
 
-        const int64 s = jd * literal64bit (86400) - literal64bit (210866803200);
+        const int64 s = (int64)(jd) * literal64bit (86400) - literal64bit (210866803200);
 
         millisSinceEpoch = 1000 * (s + (hours * 3600 + minutes * 60 + seconds - timeZoneAdjustment))
                              + milliseconds;

Besides that, it looks good for me.

I updated CalendarComponentJuceTime.7z to reflect the full inclusion of the Time extension without a config option.

Nice one, thanks.

I’m surprised that it needs that extra int64 cast - I was assuming that multiplying an int by an int64 constant would do the maths at 64-bit. I guess the compiler assumed that since the constant is small enough to fit inside an int, it can all be done in 32-bit.

heh this is 4 years old, does anyone have a working juce version of a calendar component ?

this is the original Ptomaine (hacked by gulrak) version. you will probably need to modify it slightly before being able to use it, but i think it will mainly work:

juce_CalendarComponent.zip

in the same place as usual

http://www.anticore.org/juce

Cheers !