# Module Calendar

Description

  Time and day system

      Q:  I need to parse some date in a non-strict format, like
         the one in the HTTP or mail protocol, or from a user web
         form.

      A:  Calendar.dwim_day, or Calendar.dwim_time, should solve
         your problem.

           > Calendar.dwim_day("1/2/3");
           Result: Day(Thu 2 Jan 2003)
           > Calendar.dwim_day("1 aug 2001");
           Result: Day(Wed 1 Aug 2001)

           > Calendar.dwim_time("1 aug 2001 23:14 EDT");
           Result: Minute(Wed 1 Aug 2001 23:14 EDT)
           > Calendar.dwim_time("2001 2 3 23:14:23 UTC+9");
           Result: Second(Sat 3 Feb 2001 23:14:23 UTC+9)

         If it doesn't, and it should, report the problem to me
         and I'll see what I can do. Note that the timezones
         are rather unpredictable - if it doesn't get it, you
         will get the default (local) timezone.

      -------------------------------------------------------------------------

      Q:  The dwim_* functions are too slow.

      A:  They are not written to be fast, but to do good guessing.

         If you know the format, you should use the Calendar.parse
         function:

           > Calendar.parse("%Y-%M-%D %h:%m","2040-11-08 2:46");
           Result: Minute(Thu 8 Nov 2040 2:46 CET)
           > Calendar.parse("%Y w%W %e %h:%m %p %z","1913 w4 monday 2:14 pm CET");
           Result: Minute(Mon 20 Jan 1913 14:14 CET)

         These are the format characters:
          %Y absolute year
          %y dwim year (70-99 is 1970-1999, 0-69 is 2000-2069)
          %M month (number, name or short name) (needs %y)
          %W week (needs %y)
          %D date (needs %y, %m)
          %d short date (20000304, 000304)
          %a day (needs %y)
          %e weekday (needs %y, %w)
          %h hour (needs %d, %D or %W)
          %m minute (needs %h)
          %s second (needs %m)
          %f fraction of a second (needs %s)
          %t short time (205314, 2053)
          %z zone
          %p "am" or "pm"
          %n empty string (to be put at the end of formats)

         and you can also use "%*[....]" to skip some characters,
         as in sscanf().

         If this is too slow, there is currently no solution in Pike
         to do this faster, except possibly sscanf and manual calculations/
         time object creation.

      -------------------------------------------------------------------------

      Q:  How do I get from unix time (time(2)) to a unit and back?

      A:  Calendar.Unit("unix",time())
         unit->unix_time()

           > Calendar.Day("unix",987654321);
           Result: Day(Thu 19 Apr 2001)
           > Calendar.Second("unix",987654321);
           Result: Second(Thu 19 Apr 2001 6:25:21 CEST)

           > Calendar.Day()->unix_time();
           Result: 979081200

         Note that you will get the time for the start of the unit.
         Unix time is timezone independant.

         The day-of-time units (seconds, hours, etc) uses this
         as internal representation of time.

      -------------------------------------------------------------------------

      Q:  I'm a mad astronomer, how do I do the same conversions with
         julian day numbers?

      A:  Julian day numbers are used as the internal representation
         for the day, and for most other bigger-then-time-of-day calculations.

           > Calendar.Day("julian",2454545);
           Result: Day(Wed 19 Mar 2008)
           > Calendar.Second("julian",2430122.0);
           Result: Second(Tue 6 May 1941 13:00:00 CET)

         Julian day numbers from day units and bigger are integers,
         representing the new julian number on that day. Julian day
         numbers from time of day units are represented in floats.

           > Calendar.Day()->julian_day();
           Result: 2451920
           > Calendar.Second()->julian_day();
           Result: 2451919.949595

         Watch out for the float precision, though. If you haven't
         compiled your Pike with --with-double-precision, this gives
         you awkwardly low precision - 6 hours.

      -------------------------------------------------------------------------

      Q:  How do I convert a "Second(Sat 3 Feb 2001 23:14:23 UTC+9)" object
         to my timezone?

      A:  ->set_timezone(your timezone)

           > Calendar.dwim_time("2001 2 3 23:14:23 PST")
           	  ->set_timezone("Europe/Stockholm");
           Result: Second(Sun 4 Feb 2001 8:14:23 CET)

           > Calendar.dwim_time("2001 2 3 23:14:23 PST")
           	  ->set_timezone("locale");
           Result: Second(Sun 4 Feb 2001 8:14:23 CET)

      -------------------------------------------------------------------------

      Q:  How do I print my time object?

      A:  ->format_xxx();

         You can either print it unit-sensitive,

           > Calendar.dwim_time("2001 2 3 23:14:23 PST")->format_nice();
           Result: "3 Feb 2001 23:14:23"
           > Calendar.Week()->format_nice();
           Result: "w2 2001"
           > Calendar.now()->format_nicez();
           Result: "10 Jan 10:51:15.489603 CET"

         or in a format not depending on the unit,

           > Calendar.Week()->format_ymd();
           Result: "2001-01-08"
           > Calendar.Day()->format_time();
           Result: "2001-01-10 00:00:00"

         This is all the formats:

         format_ext_time       "Wednesday, 10 January 2001 10:49:57"
         format_ext_time_short "Wed, 10 Jan 2001 10:49:57 CET"
         format_ext_ymd        "Wednesday, 10 January 2001"
         format_iso_time       "2001-01-10 (Jan) -W02-3 (Wed) 10:49:57 UTC+1"
         format_iso_ymd        "2001-01-10 (Jan) -W02-3 (Wed)"
         format_mod            "10:49"
         format_month          "2001-01"
         format_month_short    "200101"
         format_mtime          "2001-01-10 10:49"
         format_time           "2001-01-10 10:49:57"
         format_time_short     "20010110 10:49:57"
         format_time_xshort    "010110 10:49:57"
         format_tod            "10:49:57"
         format_tod_short      "104957"
         format_todz           "10:49:57 CET"
         format_todz_iso       "10:49:57 UTC+1"
         format_week           "2001-w2"
         format_week_short     "2001w2"
         format_iso_week       "2001-W02"
         format_iso_week_short "200102"
         format_xtime          "2001-01-10 10:49:57.539198"
         format_xtod           "10:49:57.539658"
         format_ymd            "2001-01-10"
         format_ymd_short      "20010110"
         format_ymd_xshort     "010110"

         format_ctime          "Wed Jan 10 10:49:57 2001\n"
         format_smtp           "Wed, 10 Jan 2001 10:49:57 +0100"
         format_http           "Wed, 10 Jan 2001 09:49:57 GMT"

      -------------------------------------------------------------------------

      Q:  How old am I?

      A:  First, you need to create the time period representing your age.

           > object t=Calendar.dwim_time("1638 dec 23 7:02 pm")
           	  ->distance(Calendar.now());
           Result: Fraction(Thu 23 Dec 1638 19:02:00.000000 LMT -
           		       Wed 10 Jan 2001 10:53:33.032856 CET)

        Now, you can ask for instance how many years this is:

           > t->how_many(Calendar.Year);
           Result: 362

        Or how many 17 seconds it is:

           > t->how_many(Calendar.Second()*17);
           Result: 672068344

        A note here is to use ->distance, and not ->range, since that
        will include the destination unit too:

          > Calendar.dwim_day("00-01-02")->range(Calendar.Week(2000,2))
             ->how_many(Calendar.Day());
          Result: 15
          > Calendar.dwim_day("00-01-02")->distance(Calendar.Week(2000,2))
             ->how_many(Calendar.Day());
          Result: 8

      -------------------------------------------------------------------------

      Q:  In 983112378 days, what weekday will it be?

      A:  (this weekday + 983112378) % 7   ;)

         or take this day, add the number, and ask the object:

           > (Calendar.Day()+983112378)->week_day_name();
           Result: "Saturday"

         "+int" will add this number of the unit to the unit;
         this means that Calendar.Year()+2 will move two years
         forward, but Calendar.now()+2 will not move at all
         - since now has zero size.

         To add a number of another time unit, simply do that:

           > Calendar.Day()+3*Calendar.Year();
           Result: Day(Sat 10 Jan 2004)
           > Calendar.Day()+3*Calendar.Minute()*134;
           Result: Minute(Wed 10 Jan 2001 6:42 CET - Thu 11 Jan 2001 6:42 CET)

         The last result here is because the resulting time still will
         be as long as the first.

      -------------------------------------------------------------------------

      Q:  Are there other calendars?

      A:  Yes.

         Calendar.Day is really a shortcut to Calendar.ISO.Day;
         this is tuned in the localization.h file.

         There is currently:

         Gregorian
      	This is the base module for Julian style calendars;
      	despite the name. Most calendars of today are in sync
      	with the Gregorian calendar.
         ISO
      	This inherits the Gregorian calendar to tweak it to
      	conform to the ISO standards. Most affected are weeks,
      	which starts on Monday in the ISO calendar.
      	This is also the default calendar.
         Discordian
      	The Discordian calendar as described in Principia Discordia
      	is in sync with the Gregorian calendar (although some claim
      	that it should be the Julian - I go with what I can read
      	from my Principia Discordia). The module inherits and
      	tweaks the Gregorian module.
         Coptic
      	The Coptic calendar is by some sources ("St. Marks'
      	Coptic Orthodox Church" web pages) is for now on in sync with
      	the Gregorian Calendar, so this module too inherits
      	and tweaks the Gregorian module. It needs to be
      	adjusted for historical use.
         Julian
      	This is the Julian calendar, with the small changes
      	to the Gregorian calendar (leap years).
         Badi (Baha'i)
             The Badi calendar used by the Baha'i religion is based on the
             solar year. For the time being it is in sync with the Gregorian
             calendar.

         Islamic
      	This is the Islamic calendar, using the 'Calendrical
      	Calculations' rules for new moon. It is based
      	directly on the YMD module.
         Stardate
      	This is the (TNG) Stardate calendar, which consists
      	of one time unit only, the Tick (1000 Tick is one earth year).
      	It is based directly on TimeRanges.

      -------------------------------------------------------------------------

      Q:  How do I convert between the calendars?

      A:  You give the unit to be converted to the constructor of
         the unit you want it to be.

         > Calendar.Coptic.Day(Calendar.dwim_day("14 feb 1983"));
         Result: Day(Mon 7 Ams 1699)
         > Calendar.Islamic.Minute(Calendar.dwim_day("14 feb 1983"));
         Result: Minute(aha 29 Rebîul-âchir 1403 AH 13:00 CET -
         		   ith 1 Djumâda'l-ûla 1403 AH 13:00 CET)
         > Calendar.Day(Calendar.Stardate.Tick(4711));
         Result: Day(Sat 17 Sep 2327 0:00 sharp)

      -------------------------------------------------------------------------

      Q:  Isn't there a <my country> calendar?

      A:  <your country> uses the ISO calendar, with just different
         names for the months. Language is a parameter to the
         calendar units, as well as the timezone.

         You set the language by using ->set_language(yourlanguage).

           > t->set_language("pt")->format_ext_ymd();
           Result: "Quarta-feira, 10 Janeiro 2001"
           > t->set_language("roman")->format_ext_ymd();
           Result: "Mercurii dies, X Ianuarius MMDCCLIII ab urbe condita"

         Note that all languages aren't supported. If you miss your
         favourite language or I got it all wrong (or have some time over
         to help me out), look in the Language.pmod file and send me an
         update.

         Or send me a list of the weekdays and month names
         (please start with Monday and January).

         Currently, these languages are supported:

           name        code
           -------------------------------
           ISO                 (default, aka English)

           Afrikaans   af afr   (South Africa),
           Austrian    de_AT
           Basque      eu eus   (Spain)
           Catalan     ca cat   (Catalonia)
           Croatian    hr hrv
           Danish      da dan
           Dutch       nl nld
           English     en eng
           Estonian    et est
           Faroese     fo fao
           Finnish     fi fin
           French      fr fra
           Galician    gl glg   (Spain)
           German      de deu
           Greenlandic kl kal
           Hungarian   hu hun
           Icelandic   is isl
           Irish       ga gle   (Gaelic)
           Italian     it ita
           Latvian     lv lav
           Lithuanian  lt lit
           Norwegian   no nor
           Persian     fa fas   (Iran)
           Polish      pl pol
           Portugese   pt por
           Romanian    ro ron
           Serbian     sr srp   (Yugoslavia)
           Slovenian   sl slv
           Spanish     es spa
           Swedish     sv swe
           Turkish     tr
           Welsh       cy cym

           Latin       la lat
           Roman              (Roman Latin)

      -------------------------------------------------------------------------

      Q:  Isn't there a <whatever> calendar?

      A:  Not if it isn't listed above. I'll appreciate any
         implementation help if you happen to have the time over
         to implement some calendar.

         I know I miss these:

           Chinese
           Jewish or Hebreic
           Maya

         Of these, the two first are based on astronomical events,
         which I haven't had the time to look into yet, but the
         last - Maya - is totally numeric.

      -------------------------------------------------------------------------

      Q:  I don't like that weeks starts on Mondays.
         Every school kids knows that weeks start on Sundays.

      A:  According to the ISO 8601 standard, weeks start on mondays.

         If you don't like it, edit the Calendar.pmod/localization.h
         file to use the Gregorian calendar instead of the ISO.

         Or use Calendar.Gregorian.Day, etc.

      -------------------------------------------------------------------------

      Q:  How do I find out which days are red in a specific region?

      A:  Events.<region>

         - contains the events for the region, as a SuperEvent.
         You can ask this object to filter out the holidays,

            Events.se->holidays();

         which will be a superevent containing only holidays.

         To use this information, you can for instance use ->scan,
         here in an example to see what red days there are in Sweden
         the current month:

           > Calendar.Events.se->filter_flag("h")->scan(Calendar.Month());
           Result: ({ /* 6 elements */
          		   Day(Sun 7 Jan 2001),
          		   Day(Sun 14 Jan 2001),
          		   Day(Sun 21 Jan 2001),
          		   Day(Sun 28 Jan 2001),
          		   Day(Sat 6 Jan 2001),
          		   Day(Mon 1 Jan 2001)
          	       })

      -------------------------------------------------------------------------

      Q:  How accurate are the events information?

      A:  For some regions, very. For most region, not very.

         The first reason is lack of information of this kind on
         the web, especially sorted into useful rules (like "the
         third monday after 23 dec", not "8 jan").

         The second reason is lack of time and interest to do
         research, which is a rather tedious job.

         If you want to help, the check your region in the
         events/regions file and send us <pike@roxen.com> a patch.

         Don't send me "the x region is all wrong!" mails without
         telling me how it should look.

      -------------------------------------------------------------------------

      Q:  My timezone says it's DST. It's wrong.

      A:  No it isn't.  But:

         o The local timezone detector failed to find your timezone by
           itself, or found the wrong timezone.

         o or you use the wrong timezone.

         To make sure the right timezone is used, use the standard
         timezone names. Those aren't "CET" or "PST", but
         "Europe/Amsterdam" or "America/Dawson".

         You can tune the default timezone by editing
         Calendar.pmod/localization.h.

         OR this may be in the future and you have a changed DST
         rule and uses an old Pike. Then you can either download
         a new version or download new timezone data files from
         the ftp address below (if the internet still is there).

FIXME

  This needs to be reformatted as documentation.

Constant nulltimerange

  ``constant` Calendar.`nulltimerange` = ```TimeRange````

Description

  This represents the null time range, which, to differ from the zero time range (the zero-length time range), isn't placed in time. This is the result of for instance `& between two strict non-overlapping timeranges - no time at all.

  It has a constant, `is_nulltimerange`, which is non-zero. ``!` on this timerange is true.