pike.git / lib / modules / Calendar.pmod / YMD.pike

version» Context lines:

pike.git/lib/modules/Calendar.pmod/YMD.pike:1: + //! + //! module Calendar + //! submodule YMD + //! + //! base for all Roman-kind of Calendars, + //! ie, one with years, months, weeks and days + //! + //! inherits Time    -  + #pike __REAL_VERSION__ +  + // #pragma strict_types +  + inherit Calendar.Time:Time; +  + #include "constants.h" +  + // ---------------- + // virtual methods to tell how this calendar works + // ---------------- +  + protected array(int) year_from_julian_day(int jd); + protected int julian_day_from_year(int year); + protected int year_remaining_days(int y,int yday); +  + protected array(int) year_month_from_month(int y,int m); // [y,m,ndays,myd] + protected array(int) month_from_yday(int y,int yday); // [m,day-of-month,ndays,myd] +  + protected array(int) week_from_week(int y,int w); // [wy,w,wd,ndays,wjd] + protected array(int) week_from_julian_day(int jd); // [wy,w,wd,ndays,wjd] +  + protected string f_month_name_from_number; + protected string f_month_shortname_from_number; + protected string f_month_number_from_name; + protected string f_month_day_name_from_number; + protected string f_week_name_from_number; + protected string f_week_day_number_from_name; + protected string f_week_day_shortname_from_number; + protected string f_week_day_name_from_number; + protected string f_year_name_from_number; + protected string f_year_number_from_name; +  +  + protected int(0..1) year_leap_year(int y); +  + protected int compat_week_day(int n); +  + // Polynomial terms for calculating the difference between TDT and UTC. + // + // The polynomials have been taken from NASA: + // @url{http://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html@} + // + // Each entry is an @expr{array(float)@}: + // @array + // @elem float 0 + // End year for the range. + // @elem float 1 + // Year offset. + // @elem float 2 + // Year divisor. + // @elem float 3.. + // Polynomial factors with the highest exponent first. + // @endarray + protected constant deltat_polynomials = ({ +  ({ -500.0, -1820.0, 100.0, +  -20.0, 0.0, 32.0, }), +  ({ 500.0, 0.0, 100.0, +  0.0090316521, 0.022174192, -0.1798452, +  -5.952053, 33.78311, -1014.41, 10583.6, }), +  ({ 1600.0, -1000.0, 100.0, +  0.0083572073, -0.005050998, -0.8503463, +  0.319781, 71.23472, -556.01, 1574.2, }), +  ({ 1700.0, -1600.0, 1.0, +  0.000140272128, -0.01532, -0.9808, 120.0, }), +  ({ 1800.0, -1700.0, 1.0, +  0.000000851788756, 0.00013336, -0.0059285, 0.1603, 8.83, }), +  ({ 1860.0, -1800.0, 1.0, +  0.000000000875, -0.0000001699, 0.0000121272, -0.00037436, +  0.0041116, 0.0068612, -0.332447, 13.72, }), +  ({ 1900.0, -1860.0, 1.0, +  0.000004288643, -0.0004473624, 0.01680668, -0.251754, 0.5737, 7.62, }), +  ({ 1920.0, -1900.0, 1.0, +  -0.000197, 0.0061966, -0.0598939, 1.494119, -2.79, }), +  ({ 1941.0, -1920.0, 1.0, +  0.0020936, -0.076100, 0.84493, 21.20, }), +  ({ 1961.0, -1950.0, 1.0, +  0.0003926188, 0.0042918455, 0.407, 29.07, }), +  ({ 1986.0, -1975.0, 1.0, +  0.001392758, 0.0038461538, 1.067, 45.45, }), +  ({ 2005.0, -2000.0, 1.0, +  0.00002373599, 0.000651814, 0.0017275, -0.060374, 0.3345, 63.86, }), +  ({ 2050.0, -2000.0, 1.0, +  0.005589, 0.32217, 62.92 }), +  ({ 2150.0, -1820.0, 100.0, +  32.0-185.724, 56.28, -20.0, }), +  ({ Math.inf, -1820.0, 100.0, +  32.0, 0.0, -20.0, }), + }); +  + //! method float deltat(int unadjusted_utc) + //! Terrestrial Dynamical Time difference from standard time. + //! + //! returns + //! An approximation of the difference between TDT and UTC + //! in fractional seconds at the specified time. + //! + //! The zero point is 1901-06-25T14:23:01 UTC + //! (unix time -2162281019), ie the accumulated number + //! of leap seconds since then is returned. + //! + //! note + //! The function is based on polynomials provided by NASA, + //! and the result may differ from actual for dates after 2004. + float deltat(int unadjusted_utc) + { +  // Approximation of the year. This ought to be good enough for +  // most purposes given the uncertainty in the table values. +  // 31556952 == 365.2425 * 24 * 60 * 60. +  float y = 1970.0 + unadjusted_utc/31556952.0; +  +  array(float) polynomial; +  int l, c, h = sizeof(deltat_polynomials); +  do { +  c = (l + h)/2; +  polynomial = deltat_polynomials[c]; +  if (y < polynomial[0]) h = c; +  else l = c + 1; +  } while (l < h); +  float u = (y + polynomial[1])/polynomial[2]; +  +  float deltat = 0.0; +  foreach(polynomial; int i; float factor) { +  if (i < 3) continue; +  deltat = deltat * u + factor; +  } +  return deltat; + } +  + //------------------------------------------------------------------------ + //! class YMD + //! Base (virtual) time period of the Roman-kind of calendar. + //! inherits TimeRange + //------------------------------------------------------------------------ +  + class YMD + { +  inherit TimeRange; +  + // --- generic for all YMD: +  +  // The correct year to use in context with yjd, yd, m and md is y. +  // The correct year to use in context with w is wy. +  // The correct year to use without context is year_no() (= either y or wy). +  +  int y; // year +  int yjd; // julian day of the first day of the year +  +  int n; // number of this in the period +  +  int jd; // julian day of first day +  int yd; // day of year (1..) +  int m; // [*] month of year (1..12?), like +  int md; // [*] day of month (1..) +  int wy; // [*] week year +  int w; // [*] week of week year (1..) +  int wd; // [*] day of week (1..7?) +  +  int mnd=CALUNKNOWN; // [*] days in current month +  int utco=CALUNKNOWN; // [*] distance to UTC +  string tzn=0; // timezone name +  +  // [*]: might be uninitialized (CALUNKNOWN) +  +  Calendar.Ruleset rules; +  constant is_ymd=1; +  + // ---------------------------------------- + // basic Y-M-D stuff + // ---------------------------------------- +  +  void create_now() +  { +  rules=default_rules; +  create_unixtime_default(time()); +  } +  +  void create_unixtime_default(int unixtime) +  { + // 1970-01-01 is julian day 2440588 +  create_julian_day( 2440588+unixtime/86400 ); + // we can't reuse this; it might not be start of day +  [int mutco,string mtzn]=rules->timezone->tz_ux(unixtime); +  int uxo=unixtime%86400-mutco; +  if (uxo<0) +  create_julian_day( 2440588+unixtime/86400-1 ); +  else if (uxo>=86400) +  create_julian_day( 2440588+unixtime/86400+1 ); +  else if (uxo==0) +  utco=mutco,tzn=mtzn; // reuse, it *is* start of day +  } +  +  void make_month() // set m and md from y and yd +  { +  int myd; +  [m,md,mnd,myd]=month_from_yday(y,yd); +  } +  +  void make_week() // set wy, w and wd from jd +  { +  int wnd,wjd; +  [wy,w,wd,wnd,wjd]=week_from_julian_day(jd); +  } +  +  int __hash() { return jd; } +  + // --- query +  + //! method float fraction_no() + //! method int hour_no() + //! method int julian_day() + //! method int leap_year() + //! method int minute_no() + //! method int month_day() + //! method int month_no() + //! method int second_no() + //! method int utc_offset() + //! method int week_day() + //! method int week_no() + //! method int year_day() + //! method int year_no() + //! method string month_name() + //! method string month_shortname() + //! method string month_day_name() + //! method string week_day_name() + //! method string week_day_shortname() + //! method string week_name() + //! method string year_name() + //! method string tzname() + //! method string tzname_iso() +  +  int julian_day() +  { +  return jd; +  } +  + //! method int unix_time() + //! Returns the unix time integer corresponding to the start + //! of the time range object. (An unix time integer is UTC.) +  +  int unix_time() +  { + // 1970-01-01 is julian day 2440588 +  int ux=(jd-2440588)*86400; +  if (utco==CALUNKNOWN) +  [utco,tzn]=rules->timezone->tz_jd(jd); +  return ux+utco; +  } +  +  int utc_offset() +  { +  if (utco==CALUNKNOWN) +  [utco,tzn]=rules->timezone->tz_jd(jd); +  return utco; +  } +  +  string tzname() +  { +  if (!tzn) +  [utco,tzn]=rules->timezone->tz_jd(jd); +  return tzn; +  } +  +  string tzname_iso() +  { +  int u=utc_offset(); +  if (!(u%3600)) +  return sprintf("UTC%+d",-u/3600); +  if (!(u%60)) +  return +  (u<0) +  ?sprintf("UTC+%d:%02d",-u/3600,(-u/60)%60) +  :sprintf("UTC-%d:%02d",u/3600,(u/60)%60); +  return +  (u<0) +  ?sprintf("UTC+%d:%02d:%02d",-u/3600,(-u/60)%60,(-u)%60) +  :sprintf("UTC-%d:%02d:%02d",u/3600,(u/60)%60,u%60); +  } +  +  int year_no() +  { +  return y>0?y:-1+y; +  } +  +  int month_no() +  { +  if (m==CALUNKNOWN) make_month(); +  return m; +  } +  +  int week_no() +  { +  if (w==CALUNKNOWN) make_week(); +  return w; +  } +  +  int month_day() +  { +  if (md==CALUNKNOWN) make_month(); +  return md; +  } +  +  int week_day() +  { +  if (wd==CALUNKNOWN) make_week(); +  return wd; +  } +  +  int year_day() +  { +  return yd; +  } +  +  string year_name() +  { +  return rules->language[f_year_name_from_number](year_no()); +  } +  +  string week_name() +  { +  if (w==CALUNKNOWN) make_week(); +  return rules->language[f_week_name_from_number](w); +  } +  +  string month_name() +  { +  if (m==CALUNKNOWN) make_month(); +  return rules->language[f_month_name_from_number](m); +  } +  +  string month_shortname() +  { +  if (m==CALUNKNOWN) make_month(); +  return rules->language[f_month_shortname_from_number](m); +  } +  +  string month_day_name() +  { +  if (mnd==CALUNKNOWN) make_month(); +  return rules->language[f_month_day_name_from_number](md,mnd); +  } +  +  string week_day_name() +  { +  if (wd==CALUNKNOWN) make_week(); +  return rules->language[f_week_day_name_from_number](wd); +  } +  +  string week_day_shortname() +  { +  if (wd==CALUNKNOWN) make_week(); +  return rules->language[f_week_day_shortname_from_number](wd); +  } +  +  int leap_year() { return year_leap_year(year_no()); } +  +  int hour_no() { return 0; } +  int minute_no() { return 0; } +  int second_no() { return 0; } +  float fraction_no() { return 0.0; } +  + //! method mapping datetime() + //! This gives back a mapping with the relevant + //! time information (representing the start of the period); + //! <pre> + //! ([ "year": int // year number (2000 AD=2000, 1 BC==0) + //! "month": int(1..) // month of year + //! "day": int(1..) // day of month + //! "yearday": int(0..) // day of year + //! "week": int(1..) // week of year + //! "week_day": int(0..) // day of week + //! "timezone": int // offset to utc, including dst + //! + //! "unix": int // unix time + //! "julian": int // julian day + //! // for compatibility: + //! "hour": 0 // hour of day, including dst + //! "minute": 0 // minute of hour + //! "second": 0 // second of minute + //! "fraction": 0.0 // fraction of second + //! ]); + //! </pre> + //! + //! note: + //! Day of week is compatible with old versions, + //! ie, 0 is sunday, 6 is saturday, so it shouldn't be + //! used to calculate the day of the week with the given + //! week number. Year day is also backwards compatible, + //! ie, one (1) less then from the year_day() function. + //! + //! note: + //! If this function is called in a Week object that begins with + //! the first week of a year, it returns the previous year if that + //! is where the week starts. To keep the representation + //! unambiguous, the returned week number is then one more than + //! the number of weeks in that year. + //! + //! E.g. Week(2008,1)->datetime() will return year 2007 and week + //! 53 since the first week of 2008 starts in 2007. +  +  mapping datetime(void|int skip_stuff) +  { +  if (m==CALUNKNOWN) make_month(); +  if (w==CALUNKNOWN) make_week(); +  +  int week; +  if (y == wy) +  week = w; +  else +  // w is the first week of the year and it begins in the +  // previous year, so we know y == wy - 1 and w == 1. +  week = week_from_week (y, 0)[1] + 1; +  +  if (skip_stuff) // called from timeofday +  return ([ "year": y, +  "month": m, +  "day": md, +  "yearday": yd-1, +  "week": week, +  "week_day": compat_week_day(wd) +  ]); +  else +  { +  return ([ "year": y, +  "month": m, +  "day": md, +  "yearday": yd-1, +  "week": week, +  "week_day": compat_week_day(wd), +  "timezone": utc_offset(), +  "julian": jd, +  "unix": unix_time(), +  // for compatibility: +  "hour": 0, +  "minute": 0, +  "second": 0, +  "fraction": 0.0 +  ]); +  } +  } +  + // --- string format ---- +  + //! method string format_iso_ymd(); + //! method string format_ymd(); + //! method string format_ymd_short(); + //! method string format_ymd_xshort(); + //! method string format_iso_week(); + //! method string format_iso_week_short(); + //! method string format_week(); + //! method string format_week_short(); + //! method string format_month(); + //! method string format_month_short(); + //! method string format_iso_time(); + //! method string format_time(); + //! method string format_time_short(); + //! method string format_time_xshort(); + //! method string format_mtime(); + //! method string format_xtime(); + //! method string format_tod(); + //! method string format_todz(); + //! method string format_xtod(); + //! method string format_mod(); + //! Format the object into nice strings; + //! <pre> + //! iso_ymd "2000-06-02 (Jun) -W22-5 (Fri)" [2] + //! ext_ymd "Friday, 2 June 2000" [2] + //! ymd "2000-06-02" + //! ymd_short "20000602" + //! ymd_xshort "000602" [1] + //! iso_week "2000-W22" + //! iso_week_short "2000W22" + //! week "2000-w22" [2] + //! week_short "2000w22" [2] + //! month "2000-06" + //! month_short "200006" [1] + //! iso_time "2000-06-02 (Jun) -W22-5 (Fri) 00:00:00 UTC+1" [2] + //! ext_time "Friday, 2 June 2000, 00:00:00" [2] + //! ctime "Fri Jun 2 00:00:00 2000\n" [2] [3] + //! http "Fri, 02 Jun 2000 00:00:00 GMT" [4] + //! time "2000-06-02 00:00:00" + //! time_short "20000602 00:00:00" + //! time_xshort "000602 00:00:00" + //! iso_short "2000-06-02T00:00:00" + //! mtime "2000-06-02 00:00" + //! xtime "2000-06-02 00:00:00.000000" + //! tod "00:00:00" + //! tod_short "000000" + //! todz "00:00:00 CET" + //! todz_iso "00:00:00 UTC+1" + //! xtod "00:00:00.000000" + //! mod "00:00" + //! </pre> + //! <tt>[1]</tt> note conflict (think 1 February 2003) + //! <br><tt>[2]</tt> language dependent + //! <br><tt>[3]</tt> as from the libc function ctime() + //! <br><tt>[4]</tt> as specified by the HTTP standard; + //! not language dependent. + //! + //! The iso variants aim to be compliant with ISO-8601. +  +  string format_iso_ymd() +  { +  if (m==CALUNKNOWN) make_month(); +  if (w==CALUNKNOWN) make_week(); +  return sprintf("%04d-%02d-%02d (%s) -W%02d-%d (%s)", +  y,m,md, +  month_shortname(), +  w,wd, // fixme - what weekday? +  week_day_shortname()); +  } +  +  string format_ext_ymd() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%s, %s %s %s", +  week_day_name(), +  month_day_name(),month_name(),year_name()); +  } +  +  string format_ctime() +  { +  return sprintf("%s %s %2d 00:00:00 %s\n", +  week_day_shortname(), +  month_shortname(), +  md, +  year_name()); +  } +  +  string format_http() +  { +  if (wd==CALUNKNOWN) make_week(); +  if (md==CALUNKNOWN) make_month(); +  +  return +  sprintf("%s, %02d %s %04d 00:00:00 GMT", +  ("SunMonTueWedThuFriSat"/3)[compat_week_day(wd)], +  md, +  ("zzzJanFebMarAprMayJunJulAugSepOctNovDec"/3)[m], +  y); +  } +  +  string format_ext_time_short() +  { +  if (wd==CALUNKNOWN) make_week(); +  if (md==CALUNKNOWN) make_month(); +  +  return +  sprintf("%s, %d %s %d 00:00:00 GMT", +  week_day_shortname(), +  month_day(),month_shortname(),y); +  } +  +  string format_ymd() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%04d-%02d-%02d",y,m,md); +  } +  +  string format_ymd_short() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%04d%02d%02d",y,m,md); +  } +  +  string format_ymd_xshort() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%02d%02d%02d",y%100,m,md); +  } +  +  string format_iso_week() +  { +  if (w==CALUNKNOWN) make_week(); +  return sprintf("%04d-W%02d",wy,w); +  } +  +  string format_iso_week_short() +  { +  if (w==CALUNKNOWN) make_week(); +  return sprintf("%04d%02d",wy,w); +  } +  +  string format_week() +  { +  if (w==CALUNKNOWN) make_week(); +  return sprintf("%04d-%s",wy,week_name()); +  } +  +  string format_week_short() +  { +  if (w==CALUNKNOWN) make_week(); +  return sprintf("%04d%s",wy,week_name()); +  } +  +  string format_month() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%04d-%02d",y,m); +  } +  +  string format_month_short() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%04d%02d",y,m); +  } +  +  string format_iso_time() +  { +  return format_iso_ymd()+" 00:00:00"; +  } +  +  string format_ext_time() +  { +  return format_ext_ymd()+" 00:00:00"; +  } +  +  string format_time() +  { +  return format_ymd()+" 00:00:00"; +  } +  +  string format_time_short() +  { +  return format_ymd_short()+" 00:00:00"; +  } +  +  string format_iso_short() +  { +  return format_ymd_short()+"T00:00:00"; +  } +  +  string format_time_xshort() +  { +  return format_ymd_xshort()+" 00:00:00"; +  } +  +  string format_mtime() +  { +  return format_ymd()+" 00:00"; +  } +  +  string format_xtime() +  { +  return format_ymd()+" 00:00:00.000000"; +  } +  +  string format_tod() +  { +  return "00:00:00"; +  } +  +  string format_tod_short() +  { +  return "000000"; +  } +  +  string format_todz() +  { +  return "00:00:00 "+tzname(); +  } +  +  string format_todz_iso() +  { +  return "00:00:00 "+tzname_iso(); +  } +  +  string format_mod() +  { +  return "00:00"; +  } +  +  string format_xtod() +  { +  return "00:00:00.000000"; +  } +  +  string format_nice(); +  string format_nicez() +  { +  return format_nice()+" "+tzname(); +  } +  +  string tzname_utc_offset() +  { +  int u=utc_offset(); +  return sprintf("%+03d%02d", -u/3600, abs(u)/60%60); +  } +  +  string format_smtp() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%s, %s %s %s 00:00:00 %s", +  week_day_shortname(), +  month_day_name(),month_shortname(),year_name(), +  tzname_utc_offset()); +  } +  +  string format_commonlog() +  { +  if (m==CALUNKNOWN) make_month(); +  return sprintf("%02d/%s/%d:%s %s", +  month_day(), month_shortname(), year_no(), format_tod(), +  tzname_utc_offset()); +  } +  +  string format_elapsed() +  { +  return sprintf("%dd",number_of_days()); +  } +  + // --- size and move --- +  +  protected TimeRange _set_size(int n,TimeRange t) +  { +  if (t->is_timeofday) +  return second()->set_size(n,t); +  +  if (yd==1 && t->is_year) +  return Year("ymd_y",rules,y,yjd,t->n*n) +  ->autopromote(); +  + // months are even on years +  if (t->is_year || t->is_month) +  { +  if (md==CALUNKNOWN) make_month(); +  if (md==1) +  return Month("ymd_yjmw",rules,y,yjd,jd,m, +  t->number_of_months()*n,wd,w,wy) +  ->autopromote(); +  } +  + // weeks are not +  if (t->is_week) +  { +  if (wd==CALUNKNOWN) make_week(); +  if (wd==1) return Week("ymd_yjwm",rules,y,yjd,jd,wy,w,t->n*n,md,m,mnd); +  } +  + // fallback on days +  if (t->is_ymd) +  return Day("ymd_ydmw",rules,y,yjd,jd,yd, +  n*t->number_of_days(),m,md,wy,w,wd,mnd); +  +  error("set_size: incompatible class %O\n", +  object_program(t)); +  } +  +  protected TimeRange _add(int _n,TimeRange step) +  { +  if (step->is_ymd) +  return _move(_n,step); +  if (step->is_timeofday) +  if (n) +  return second()->range(second(-1))->add(_n,step); +  else +  return second()->beginning()->add(_n,step); +  +  error("add: incompatible class %O\n", +  object_program(step)); +  } +  +  array(int(-1..1)) _compare(TimeRange with) +  { +  if (objectp(with)) +  if (with->is_timeofday) +  { +  // wrap +  array(int(-1..1)) cmp=with->_compare(this); +  +  return ({-cmp[0], +  -cmp[2], +  -cmp[1], +  -cmp[3]}); +  } +  else if (with->is_ymd || with->julian_day) +  { + #define CMP(A,B) ( ((A)<(B))?-1:((A)>(B))?1:0 ) +  +  int b1=julian_day(); +  int e1=b1+number_of_days(); +  +  int b2=with->julian_day(); +  int e2=b2+with->number_of_days(); +  +  return ({ CMP(b1,b2),CMP(b1,e2),CMP(e1,b2),CMP(e1,e2) }); +  } +  return ::_compare(with); +  } +  + // --- to other YMD +  + // years +  +  int number_of_years() +  { +  int m=number_of_days(); +  if (m<=1 || m+yd-1<year()->number_of_days()) return 1; +  return 1+y-year_from_julian_day(jd+m-1)[0]; +  } +  +  array(cYear) years(void|int from, void|int to) +  { +  int n=number_of_years(); +  +  if (zero_type (from)) { +  from = 1; +  to = n; +  } +  else +  if (zero_type (to)) +  error("Illegal numbers of arguments to years()\n"); +  else +  { +  if (from>n) return ({}); else if (from<1) from=1; +  if (to>n) to=n; else if (to<from) return ({}); +  } +  +  return map(enumerate(1+to-from,1,y+from-1), +  lambda(int x) +  { return Year("ymd_yn",rules,x,1); }); +  } +  +  cYear year(void|int m) +  { +  if (zero_type (m)) m=1; +  +  if (!n&&m==-1) +  return Year("ymd_y",rules,y,yjd,1); +  +  if (m<0) m += 1 + number_of_years(); +  +  array(TimeRange) res=years(m,m); +  if (sizeof(res)==1) return res[0]; +  error("Not in range (Year 1..%d exist)\n", +  number_of_years()); +  } +  +  + // days +  +  int number_of_days(); +  +  array(cDay) days(void|int from, void|int to) +  { +  int n=number_of_days(); +  +  if (zero_type (from)) { +  from = 1; +  to = n; +  } +  else +  if (zero_type (to)) +  error("Illegal number of arguments to days()\n"); +  else +  { +  if (from>n) return ({}); else if (from<1) from=1; +  if (to>n) to=n; else if (to<from) return ({}); +  } +  +  int zy=y; +  int zyd=yd+from-1; +  int zjd=jd+from-1; +  int zyjd=yjd; +  array(cDay) res=({}); +  +  to-=from-1; +  +  for (;;) +  { +  int rd=year_remaining_days(zy,zyd)+1; +  +  if (rd>0) +  { +  if (rd>to) rd=to; +  res+=map(enumerate(rd,1,zyd), +  lambda(int x) +  { return Day("ymd_yd",rules,zy,zyjd,zyjd+x-1,x,1); }); +  if (rd==to) break; +  zjd+=rd; +  to-=rd; +  } +  +  [zy,zyjd]=year_from_julian_day(zjd); +  zyd=zjd-zyjd+1; +  } +  return res; +  } +  +  cDay day(void|int m, mixed... ignored) +  { +  if (zero_type (m)) m=1; +  +  if (!n) +  return Day("ymd_yd",rules,y,yjd,jd,yd,1); +  +  if (m<0) m+=1+number_of_days(); +  +  array(TimeRange) res=days(m,m); +  if (sizeof(res)==1) return res[0]; +  if(number_of_days()) +  error("Not in range (Day 1..%d exist).\n", +  number_of_days()); +  else +  error("No days in object.\n"); +  } +  + // --- months +  +  int number_of_months(); +  +  array(cMonth) months(int ...range) +  { +  int from=1,n=number_of_months(),to=n; +  +  if (sizeof(range)) +  if (sizeof(range)<2) +  error("Illegal numbers of arguments to months()\n"); +  else +  { +  [from,to]=range; +  if (from>n) return ({}); else if (from<1) from=1; +  if (to>n) to=n; else if (to<from) return ({}); +  } +  +  if (md==CALUNKNOWN) make_month(); +  +  return map(enumerate(1+to-from,1,from+m-1), +  lambda(int x) +  { return Month("ymd_ym",rules,y,x,1); }); +  } +  +  cMonth month(int ... mp) +  { +  if (md==CALUNKNOWN) make_month(); +  +  if (!sizeof(mp)) +  return Month("ymd_ym",rules,y,m,1); +  +  int num=mp[0]; +  +  if (num==-1 && !n) +  return Month("ymd_ym",rules,y,m,1); +  +  if (num<0) num+=1+number_of_months(); +  +  array(TimeRange) res=months(num,num); +  if (sizeof(res)==1) return res[0]; +  error("not in range; Month 1..%d exist in %O\n", +  number_of_months(),this); +  } +  + //---- week +  +  int number_of_weeks(); +  +  array(cWeek) weeks(int ...range) +  { +  int from=1,n=number_of_weeks(),to=n; +  +  if (sizeof(range)) +  if (sizeof(range)<2) +  error("Illegal numbers of arguments to weeks()\n"); +  else +  { +  [from,to]=range; +  if (from>n) return ({}); else if (from<1) from=1; +  if (to>n) to=n; else if (to<from) return ({}); +  } +  +  if (wd==CALUNKNOWN) make_week(); +  +  return map(enumerate(1+to-from,1,from+w-1), +  lambda(int x) +  { return Week("ymd_yw",rules,wy,x,1); }); +  } +  +  cWeek week(int ... mp) +  { +  if (wd==CALUNKNOWN) make_week(); +  +  if (!sizeof(mp)) +  return Week("ymd_yw",rules,wy,w,1); +  +  int num=mp[0]; +  +  if (num==-1 && !n) +  return Week("ymd_yw",rules,wy,w,1); +  +  if (num<0) num+=1+number_of_weeks(); +  +  array(TimeRange) res=weeks(num,num); +  if (sizeof(res)==1) return res[0]; +  error("not in range (Week 1..%d exist)\n", +  number_of_weeks()); +  } +  +  + // --- functions to conform to Time.* +  +  protected TimeRange get_unit(string unit,int m) +  { +  if (!n) return day()[unit](); +  if (m<0) m+=::`[]("number_of_"+unit+"s")(); +  array(TimeRange) res=::`[](unit+"s")(m,m); +  if (sizeof(res)==1) return res[0]; +  error("not in range ("+unit+" 0..%d exist)\n", +  ::`[]("number_of_"+unit+"s")()-1); +  } +  +  protected array(TimeRange) get_timeofday(string unit, +  int start,int step,program p, +  int ... range) +  { +  int from=0,n=::`[]("number_of_"+unit)(),to=n-1; +  +  if (sizeof(range)) +  if (sizeof(range)<2) +  error("Illegal numbers of arguments to "+unit+"()\n"); +  else +  { +  [from,to]=range; +  if (from>=n) return ({}); else if (from<0) from=0; +  if (to>=n) to=n-1; else if (to<from) return ({}); +  } +  +  from*=step; +  to*=step; +  +  to-=from-step; +  +  from+=unix_time(); +  +  array z= +  map(enumerate(to/step,step,from), +  lambda(int x) +  { return p("timeofday",rules,x,step); }); +  + // return z; +  +  if (sizeof(z)>1 && +  ((p==cHour && z[0]->utc_offset()%3600 != z[-1]->utc_offset()%3600) || +  (p==cMinute && z[0]->utc_offset()%60 != z[-1]->utc_offset()%60))) +  { +  // we're in a zone shifting, and we shift a non-hour (or non-minute) +  cSecond sec=Second(); +  int i,uo=z[0]->utc_offset(); +  for (i=1; i<sizeof(z); i++) +  if (z[i]->utc_offset()!=uo) +  { +  int uq=(z[i]->utc_offset()-uo); +  if (uq<0) +  { +  if (uq<=-step) uq=-(-uq%step); +  z= z[..i-1]+ +  ({z[i]->set_size(step+uq,sec)})+ +  map(z[i+1..],"add",uq,sec); +  } +  else +  { +  if (uq>=step) uq%=step; +  z= z[..i-1]+ +  ({z[i]->set_size(uq,sec)})+ +  map(z[i..<1],"add",uq,sec); +  i++; +  } +  uo=z[i]->utc_offset(); +  } +  } +  return z; +  } +  + //! method Second second() + //! method Second second(int n) + //! method Minute minute(int hour,int minute,int second) + //! method array(Second) seconds() + //! method array(Second) seconds(int first,int last) + //! method int number_of_seconds() + //! method Minute minute() + //! method Minute minute(int n) + //! method Minute minute(int hour,int minute) + //! method array(Minute) minutes() + //! method array(Minute) minutes(int first,int last) + //! method int number_of_minutes() + //! method Hour hour() + //! method Hour hour(int n) + //! method array(Hour) hours() + //! method array(Hour) hours(int first,int last) + //! method int number_of_hours() +  +  int number_of_hours() { return (number_of_seconds()+3599)/3600; } +  cHour hour(void|int n) { return get_unit("hour",n); } +  array(cHour) hours(int ...range) +  { return get_timeofday("hours",0,3600,Hour,@range); } +  +  int number_of_minutes() { return (number_of_seconds()+59)/60; } +  cMinute minute(void|int n,int ... time) +  { +  if (sizeof(time)) +  return minute(n*60+time[0]); +  return get_unit("minute",n); +  } +  array(cMinute) minutes(int ...range) +  { return get_timeofday("minutes",0,60,Minute,@range); } +  +  int number_of_seconds() { return end()->unix_time()-unix_time(); } +  cSecond second(void|int n,int ...time) +  { +  if (sizeof(time)==2) +  { +  return second(n*3600+time[0]*60+time[1]); + // return hour(n)->minute(time[0])->second(time[1]); +  } +  return get_unit("second",n); +  } +  array(cSecond) seconds(int ...range) +  { return get_timeofday("seconds",0,1,Second,@range); } +  +  float number_of_fractions() { return (float)number_of_seconds(); } +  cSecond fraction(void|float|int n) +  { +  return fractions()[0]; +  } +  array(cSecond) fractions(int|float ...range) +  { +  float from,to,n=number_of_fractions(); +  if (sizeof(range)==2) +  from=(float)range[0],to=(float)range[1]; +  else if (sizeof(range)==0) +  from=0.0,to=n; +  else +  error("Illegal arguments\n"); +  if (from<0.0) from=0.0; +  if (to>n) to=n; +  return ({Fraction("timeofday_f",rules,unix_time(),0, +  (int)to,(int)(inano*(to-(int)to))) +  ->autopromote()}); +  } +  +  TimeRange `*(int|float n) +  { +  if(intp(n)) +  return set_size(n,this); +  else +  return second()*(int)(how_many(Second)*n); +  } +  +  array(TimeRange) split(int|float n, void|function|TimeRange with) +  { +  if(!with) +  with=Second(); +  else if (functionp(with)) +  with=promote_program(with); +  +  int length=(int)(how_many(with)/n); +  int remains; +  if(length && intp(n)) +  remains=(int)(how_many(with)%n); +  if(!length) +  length=1; +  +  TimeRange start=beginning(); +  TimeRange end=end(); +  array result=({}); +  TimeRange next; +  while((next=start+with*(length+!!remains)) < end) +  { +  result += ({ start->distance(next) }); +  start=next; +  if(remains) +  remains--; +  } +  result += ({ start->distance(end) }); +  return result; +  } +  + // ---------------------------------------- + // virtual functions needed + // ---------------------------------------- +  +  string nice_print(); +  protected string _sprintf(int t,mapping m) +  { +  switch (t) +  { +  case 't': +  return "Calendar."+calendar_name()+".YMD"; +  default: +  return ::_sprintf(t,m); +  } +  } +  +  void create_julian_day(int|float jd); +  protected TimeRange _move(int n,YMD step); +  TimeRange place(TimeRange what,void|int force); +  + // not needed +  +  YMD autopromote() { return this; } + } +  + //------------------------------------------------------------------------ + //! class Year + //! This is the time period of a year. + //! inherits TimeRange + //! inherits YMD + //------------------------------------------------------------------------ +  + function(mixed...:cYear) Year=cYear; + class cYear + { +  inherit YMD; +  +  constant is_year=1; +  + // --- +  + //! + //! method void create("unix",int unix_time) + //! method void create("julian",int|float julian_day) + //! method void create(int year) + //! method void create(string year) + //! method void create(TimeRange range) + //! It's possible to create the standard year + //! by using three different methods; either the normal + //! way - from standard unix time or the julian day, + //! and also, for more practical use, from the year number. + //! +  +  protected void create(mixed ...args) +  { +  if (!sizeof(args)) +  { +  create_now(); +  return; +  } +  else switch (args[0]) +  { +  case "ymd_y": +  rules=args[1]; +  y=args[2]; +  jd=yjd=args[3]; +  n=args[4]; +  m=md=wy=w=wd=CALUNKNOWN; +  yd=1; +  return; +  case "ymd_yn": +  rules=args[1]; +  y=args[2]; +  jd=yjd=julian_day_from_year(y); +  n=args[3]; +  m=md=wy=w=wd=CALUNKNOWN; +  yd=1; +  return; +  case "unix": case "unix_r": case "julian": case "julian_r": +  // Handled by ::create. +  break; +  default: +  if (intp(args[0]) && sizeof(args)==1) +  { +  rules=default_rules; +  y=args[0]; +  jd=yjd=julian_day_from_year(y); +  n=1; +  m=md=wy=w=wd=CALUNKNOWN; +  yd=1; +  return; +  } +  else if (stringp(args[0])) +  { +  y=default_rules->language[f_year_number_from_name](args[0]); +  rules=default_rules; +  jd=yjd=julian_day_from_year(y); +  n=1; +  m=md=wy=w=wd=CALUNKNOWN; +  yd=1; +  return; +  } +  break; +  +  } +  rules=default_rules; +  ::create(@args); +  } +  +  void create_julian_day(int|float _jd) +  { +  if (floatp(_jd)) +  create_unixtime_default((int)((jd-2440588)*86400)); +  else +  { +  [y,yjd]=year_from_julian_day(_jd); +  jd=yjd; +  n=1; +  md=yd=m=1; +  wy=w=wd=CALUNKNOWN; // unknown +  } +  } +  +  TimeRange beginning() +  { +  return Year("ymd_y",rules,y,yjd,0); +  } +  +  TimeRange end() +  { +  return Year("ymd_yn",rules,y+n,0); +  } +  + // ---------------- +  +  protected string _sprintf(int t,mapping m) +  { +  switch (t) +  { +  case 'O': +  if (n!=1) +  return sprintf("Year(%s)",nice_print_period()); +  return sprintf("Year(%s)",nice_print()); +  case 't': +  return "Calendar."+calendar_name()+".Year"; +  default: +  return ::_sprintf(t,m); +  } +  } +  +  string nice_print_period() +  { +  if (!n) return nice_print()+" sharp"; +  return sprintf("%s..%s",nice_print(),year(-1)->nice_print()); +  } +  +  string nice_print() +  { +  return year_name(); +  } +  +  string format_nice() +  { +  return year_name(); +  } +  + // --- Year _move +  +  TimeRange _move(int m,YMD step) +  { +  if (!step->n || !m) +  return this; +  +  if (step->is_year) +  return Year("ymd_yn",rules,y+m*step->n,n) +  ->autopromote(); +  +  if (step->is_month) +  return month()->add(m,step)->set_size(this); +  + // if (step->is_week) + // return week()->add(m,step)->set_size(this); +  +  if (step->is_ymd) +  return Day("ymd_jd",rules, +  yjd+m*step->number_of_days(),number_of_days()) +  ->autopromote(); +  +  error("_move: Incompatible type %O\n",step); +  } +  +  protected void convert_from(TimeRange other) +  { + #if 0 +  // The following is disabled since it leads to inconsistent +  // behavior with other time ranges when they are converted to +  // partly overlapping time ranges. If the user wants to convert +  // a week to the year it "unambiguously" belongs to, (s)he can +  // do Calendar.ISO.Year(week->year_no()). +  if (other->is_week) { +  // Weeks aren't even on years but they still unambiguously +  // belong to years. We therefore convert using the week year +  // instead of the default method that uses the julian day. +  create ("ymd_yn", other->rules, other->wy, +  other->n > 1 ? +  other->week(-1)->wy - other->wy + 1 : +  other->n); +  return; +  } + #endif +  +  ::convert_from(other); +  if (other->number_of_years) +  n=other->number_of_years(); +  else +  n=0; +  } +  +  TimeRange place(TimeRange what,void|int force) +  { +  if (what->is_day) +  { +  int yd=what->yd; +  return Day("ymd_yd",rules,y,yjd,yjd+yd-1,yd,what->n); +  } +  +  if (what->is_week) +  { +  cWeek week=Week("ymd_yw",rules,y,what->w,what->n); +  if (!force && week->wy!=y) return 0; // not this year +  return week; +  } +  +  if (what->is_year) +  return Year("ymd_yn",rules,y,what->number_of_years()); +  +  if (what->is_month) +  return month(what->month_name()); +  +  if (what->is_timeofday) +  return place(what->day(),force)->place(what,force); +  +  error("place: Incompatible type %O\n",what); +  } +  +  TimeRange distance(TimeRange to) +  { +  if (to->is_timeofday) +  { +  return hour()->distance(to); +  } +  if (to->is_ymd) +  { +  if (to->is_year) +  { +  int y1=y; +  int y2=to->y; +  if (y2<y1) +  error("distance: negative distance\n"); +  return Year("ymd_yn",rules,y,y2-y1) +  ->autopromote(); +  } +  if (to->is_month) +  return month()->distance(to); +  return day()->distance(to); +  } +  +  error("distance: Incompatible type %O\n",to); +  } +  + // --- +  +  int number_of_years() +  { +  return n; +  } +  +  int number_of_weeks(); +  + //! method Month month() + //! method Month month(int n) + //! method Month month(string name) + //! The Year type overloads the month() method, + //! so it is possible to get a specified month + //! by string: + //! + //! <tt>year-&gt;month("April")</tt> + //! + //! The integer and no argument behavior is inherited + //! from <ref to=YMD.month>YMD</ref>(). +  +  cMonth month(int|string ... mp) +  { +  if (sizeof(mp) && +  stringp(mp[0])) +  { +  int num=((int)mp[0]) || +  rules->language[f_month_number_from_name](mp[0]); +  if (!num) +  error("no such month %O in %O\n",mp[0],this); +  +  return ::month(num); +  } +  else +  return ::month(@mp); +  } +  + //! method Week week() + //! method Week week(int n) + //! method Week week(string name) + //! The Year type overloads the week() method, + //! so it is possible to get a specified week + //! by name: + //! + //! <tt>year-&gt;week("17")</tt> + //! <tt>year-&gt;week("w17")</tt> + //! + //! The integer and no argument behavior is inherited + //! from <ref to=YMD.week>YMD</ref>(). + //! + //! This is useful, since the first week of a year + //! not always (about half the years, in the ISO calendar) + //! is numbered '1'. + //! +  +  cWeek week(int|string ... mp) +  { +  if (sizeof(mp) && +  stringp(mp[0])) +  { +  int num; +  sscanf(mp[0],"%d",num); +  sscanf(mp[0],"w%d",num); +  +  cWeek w=::week(num); +  if (w->week_no()==num) return w; +  return ::week(num-(w->week_no()-num)); +  } +  else +  return ::week(@mp); +  } +  +  cYear set_ruleset(Calendar.Ruleset r) +  { +  return Year("ymd_y",r,y,yjd,n); +  } + } +  +  + // ---------------------------------------------------------------- + //! class Month + //! inherits YMD + // ---------------------------------------------------------------- +  + function(mixed...:cMonth) Month=cMonth; + class cMonth + { +  inherit YMD; +  +  constant is_month=1; +  +  int nd; // number of days +  int nw; // number of weeks +  +  protected void create(mixed ...args) +  { +  if (!sizeof(args)) +  { +  rules=default_rules; +  create_unixtime_default(time()); +  return; +  } +  else +  switch (args[0]) +  { +  case "ymd_ym": +  rules=args[1]; +  y=args[2]; +  m=args[3]; +  n=args[4]; +  md=1; +  wy=w=wd=CALUNKNOWN; +  [y,m,nd,yd]=year_month_from_month(y,m); +  yjd=julian_day_from_year(y); +  jd=yjd+yd-1; +  if (n!=1) nd=CALUNKNOWN; +  nw=CALUNKNOWN; +  return; +  case "ymd_yjmw": +  rules=args[1]; +  y=args[2]; +  yjd=args[3]; +  jd=args[4]; +  yd=1+jd-yjd; +  m=args[5]; +  n=args[6]; +  wd=args[7]; +  w=args[8]; +  wy=args[9]; +  md=1; +  nw=nd=CALUNKNOWN; +  return; +  case "ymd_jd": +  rules=args[1]; +  create_julian_day(args[2]); +  n=args[3]; +  return; +  case "unix": case "unix_r": case "julian": case "julian_r": +  // Handled by ::create. +  break; +  default: +  if (intp(args[0]) && sizeof(args)==2) +  { +  create("ymd_ym",default_rules,args[0],args[1],1); +  if (y!=args[0]) +  error("month %d doesn't exist in %d\n",args[1],args[0]); +  return; +  } +  break; +  } +  +  rules=default_rules; +  ::create(@args); +  } +  +  void create_julian_day(int|float _jd) +  { +  if (floatp(_jd)) +  create_unixtime_default((int)((jd-2440588)*86400)); +  else +  { +  int zmd; +  [y,yjd]=year_from_julian_day(jd=_jd); +  [m,zmd,nd,yd]=month_from_yday(y,1+jd-yjd); +  jd=yd+yjd-1; +  +  n=1; +  md=1; +  nw=wd=w=wy=CALUNKNOWN; // unknown +  } +  } +  +  protected string _sprintf(int t,mapping m) +  { + // return sprintf("month y=%d yjd=%d m=%d jd=%d yd=%d n=%d nd=%d", + // y,yjd,m,jd,yd,n,number_of_days()); +  switch (t) +  { +  case 'O': +  if (n!=1) +  return sprintf("Month(%s)",nice_print_period()); +  return sprintf("Month(%s)",nice_print()); +  case 't': +  return "Calendar."+calendar_name()+".Month"; +  default: +  return ::_sprintf(t,m); +  } +  } +  +  string nice_print() +  { +  return sprintf("%s %s", month_name(), year_name()); +  } +  +  string format_nice() +  { +  return sprintf("%s %s", month_name(), year_name()); +  } +  +  +  string nice_print_period() +  { +  if (!n) return day()->nice_print()+" "+minute()->nice_print()+" sharp"; +  cMonth mo=month(-1); +  if (mo->y==y) +  return sprintf("%s..%s %s", +  month_shortname(), +  mo->month_shortname(), +  year_name()); +  return nice_print()+" .. "+mo->nice_print(); +  } +  +  TimeRange beginning() +  { +  return Month("ymd_yjmw",rules,y,yjd,jd,m,0,wd,w,wy) +  ->autopromote(); +  } +  +  TimeRange end() +  { +  return Month("ymd_ym",rules,y,m+n,0) +  ->autopromote(); +  } +  + // --- month position and distance +  +  TimeRange distance(TimeRange to) +  { +  if (to->is_timeofday) +  return hour()->distance(to); +  +  if (to->is_ymd) +  { +  if (to->is_month || to->is_year) +  { +  int n1=months_to_month(to->y,to->is_year?1:to->m); +  if (n1<0) +  error("distance: negative distance (%d months)\n",n1); +  return Month("ymd_yjmw",rules,y,yjd,jd,m,n1,wd,w,wy) +  ->autopromote(); +  } +  +  int d1=jd; +  int d2=to->jd; +  if (d2<d1) +  error("distance: negative distance (%d days)\n",d2-d1); +  return Day("ymd_ydmw",rules,y,yjd,jd,yd,d2-d1,m,1,wy,w,wd,mnd) +  ->autopromote(); +  } +  +  error("distance: Incompatible type %O\n",to); +  } +  +  protected void convert_from(TimeRange other) +  { +  ::convert_from(other); +  if (other->number_of_months) +  n=other->number_of_months(); +  else +  n=0; +  } +  +  TimeRange _move(int x,YMD step) +  { +  if (step->is_year) +  return Month("ymd_ym",rules,y+x*step->n,m,n) +  ->autopromote(); +  if (step->is_month) +  return Month("ymd_ym",rules,y,m+x*step->n,n) +  ->autopromote(); +  +  return Day("ymd_jd",rules,jd+x*step->number_of_days(),number_of_days()) +  ->autopromote(); +  } +  +  TimeRange place_day(int day,int day_n,void|int force) +  { +  if (day>number_of_days()) return 0; // doesn't exist +  return Day("ymd_jd",rules,jd+day-1,day_n)->autopromote(); +  } +  +  TimeRange place(TimeRange what,void|int force) +  { +  if (what->is_year) +  return year()->place(what,force); // just fallback +  +  if (what->is_day) +  return place_day(what->month_day(),what->n,force); +  +  if (what->is_month) +  return Month("ymd_ym",rules,y,m,what->number_of_months()) +  ->autopromote(); +  +  if (what->is_week) +  return place(what->day(),force)->week(); +  +  if (what->is_timeofday) +  return place(what->day(),force)->place(what,force); +  +  error("place: Incompatible type %O\n",what); +  } +  + // --- Month to other units +  +  int number_of_years() +  { +  if (n<=1) return 1; +  +  [int y2,int m2,int nd2,int yd2]=year_month_from_month(y,m+n); +  return 1+y2-y; +  } +  +  int number_of_days() +  { +  if (nd!=CALUNKNOWN) return nd; +  +  [int y2,int m2,int nd2,int yd2]=year_month_from_month(y,m+n); +  return nd=julian_day_from_year(y2)+yd2-jd-1; +  } +  +  int number_of_weeks() +  { +  if (nw!=CALUNKNOWN) return nw; +  +  [int y2,int m2,int nd2,int yd2]=year_month_from_month(y,m+n); +  +  return nw= +  Week("julian_r",jd,rules) +  ->range(Week("julian_r",julian_day_from_year(y2)+yd2-2,rules)) +  ->number_of_weeks(); +  } +  +  int number_of_months() +  { +  return n; +  } +  +  cMonth set_ruleset(Calendar.Ruleset r) +  { +  return Month("ymd_yjmw",r,y,yjd,jd,m,n,wd,w,wy); +  } +  + // --- needs to be defined +  +  protected int months_to_month(int y,int m); + } +  + // ---------------------------------------------------------------- + //! class Week + //! The Calendar week represents a standard time period of + //! a week. In the Gregorian calendar, the standard week + //! starts on a sunday and ends on a saturday; in the ISO + //! calendar, it starts on a monday and ends on a sunday. + //! + //! The week are might not be aligned to the year, and thus + //! the week may cross year borders and the year of + //! the week might not be the same as the year of all the + //! days in the week. The basic rule is that the week year + //! is the year that has the most days in the week, but + //! since week number only is specified in the ISO calendar + //! - and derivates - the week number of most calendars + //! is the week number of most of the days in the ISO + //! calendar, which modifies this rule for the Gregorian calendar; + //! the week number and year is the same as for the ISO calendar, + //! except for the sundays. + //! + //! When adding, moving and subtracting months + //! to a week, it falls back to using days. + //! + //! When adding, moving or subtracting years, + //! if tries to place the moved week in the + //! resulting year. + //! + //! inherits YMD + // ---------------------------------------------------------------- +  + function(mixed...:cWeek) Week=cWeek; + class cWeek + { +  inherit YMD; +  +  // Note: wy, w and wd are never CALUNKNOWN in this class. +  +  constant is_week=1; +  + //! + //! method void create("unix",int unix_time) + //! method void create("julian",int|float julian_day) + //! method void create(int year,int week) + //! It's possible to create the standard week + //! by using three different methods; either the normal + //! way - from standard unix time or the julian day, + //! and also, for more practical use, from year and week + //! number. + //! +  +  protected void create(mixed ...args) +  { +  if (!sizeof(args)) +  { +  rules=default_rules; +  create_unixtime_default(time()); +  return; +  } +  else +  switch (args[0]) +  { +  case "ymd_yw": +  rules=args[1]; +  wy=args[2]; +  w=args[3]; +  n=args[4]; +  m=md=CALUNKNOWN; +  [wy,w,wd,int nd,jd]=week_from_week(wy,w); +  int wyjd=julian_day_from_year(wy); +  if (wyjd > jd) +  y = wy - 1, yjd = julian_day_from_year (y); +  else +  y = wy, yjd = wyjd; +  yd=1+jd-yjd; +  if (n!=1) nd=CALUNKNOWN; +  return; +  case "ymd_yjwm": +  rules=args[1]; +  y=args[2]; +  yjd=args[3]; +  jd=args[4]; +  yd=1+jd-yjd; +  wy=args[5]; +  w=args[6]; +  n=args[7]; +  md=args[8]; +  m=args[9]; +  mnd=args[10]; +  wd=1; +  nd=CALUNKNOWN; +  return; +  case "ymd_jd": +  rules=args[1]; +  create_julian_day(args[2]); +  n=args[3]; +  return; +  case "unix": case "unix_r": case "julian": case "julian_r": +  // Handled by ::create. +  break; +  default: +  if (intp(args[0]) && sizeof(args)==2) +  { +  create("ymd_yw",default_rules,args[0],args[1],1); +  if (wy!=args[0]) +  // FIXME: Allow weeks 0 and 53/54 if they contain +  // at least one day in this year. +  error("Week %d doesn't exist in %d\n",args[1],args[0]); +  return; +  } +  break; +  } +  +  rules=default_rules; +  ::create(@args); +  } +  +  void create_julian_day(int|float _jd) +  { +  if (floatp(_jd)) +  create_unixtime_default((int)((jd-2440588)*86400)); +  else +  { +  int zwd; +  [wy,w,zwd,int nd,jd]=week_from_julian_day(_jd); +  [y,yjd]=year_from_julian_day(_jd); +  yd=1+jd-yjd; +  +  n=1; +  wd=1; +  md=m=CALUNKNOWN; // unknown +  } +  } +  +  protected string _sprintf(int t,mapping m) +  { + // return sprintf("week y=%d yjd=%d w=%d jd=%d yd=%d n=%d nd=%d", + // y,yjd,w,jd,yd,n,number_of_days()); +  switch (t) +  { +  case 'O': +  if (n!=1) +  return sprintf("Week(%s)",nice_print_period()); +  return sprintf("Week(%s)",nice_print()); +  case 't': +  return "Calendar."+calendar_name()+".Week"; +  default: +  return ::_sprintf(t,m); +  } +  } +  +  int year_no() +  { +  return wy>0?wy:-1+wy; +  } +  +  int year_day() +  //! Can be less than 1 for the first week of the year if it begins +  //! in the previous year. +  { +  return y != wy ? 1 + jd - julian_day_from_year (wy) : yd; +  } +  +  string nice_print() +  { +  return +  sprintf("%s %s", +  week_name(), +  year_name()); +  } +  +  string format_nice() +  { +  return +  sprintf("%s %s", +  week_name(), +  year_name()); +  } +  +  +  string nice_print_period() +  { +  if (!n) return day()->nice_print()+" "+minute()->nice_print()+" sharp"; +  cWeek wo=week(-1); +  if (wo->wy==wy) +  return sprintf("%s..%s %s", +  week_name(), +  wo->week_name(), +  year_name()); +  return nice_print()+" .. "+wo->nice_print(); +  } +  +  TimeRange beginning() +  { +  return Week("ymd_yjwm",rules,y,yjd,jd,wy,w,0,md,m,mnd) +  ->autopromote(); +  } +  +  TimeRange end() +  { +  return Week("ymd_yw",rules,wy,w+n,0) +  ->autopromote(); +  } +  + // --- week position and distance +  +  TimeRange distance(TimeRange to) +  { +  if (to->is_timeofday) +  return hour()->distance(to); +  +  if (to->is_week) +  { +  int n1=weeks_to_week(to->wy,to->w); +  if (n1<0) +  error("distance: negative distance (%d weeks)\n",n1); +  return Week("ymd_yjwm",rules,y,yjd,jd,wy,w,n1,md,m,mnd) +  ->autopromote(); +  } +  +  if (to->julian_day) +  { +  int d1=jd; +  int d2=to->julian_day(); +  if (d2<d1) +  error("distance: negative distance (%d days)\n",d2-d1); +  return Day("ymd_ydmw",rules,y,yjd,jd,yd,d2-d1,m,md,wy,w,1,mnd) +  ->autopromote(); +  } +  +  error("distance: Incompatible type %O\n",to); +  } +  +  protected void convert_from(TimeRange other) +  { +  ::convert_from(other); +  if (other->number_of_weeks) +  n=other->number_of_weeks(); +  else +  n=0; +  } +  +  TimeRange _move(int x,YMD step) +  { +  if (step->is_week) +  return Week("ymd_yw",rules,wy,w+x*step->n,n) +  ->autopromote(); +  +  if (step->is_year) { +  TimeRange stepped = year()->add(x,step); +  if (TimeRange placed = stepped->place(this,0)) +  return placed; +  // If we couldn't place our week in the target year it means +  // we're in week 53 and the target year got only 52 weeks. We +  // return the closest week of the target year, i.e. week 52. +  return Week ("ymd_yw", rules, stepped->y, 52, n); +  } +  +  if (step->number_of_days) +  return Day("ymd_jd",rules, +  jd+x*step->number_of_days(),number_of_days()) +  ->autopromote(); +  +  error("add: Incompatible type %O\n",step); +  } +  +  TimeRange place_day(int day,int day_n,int force) +  { +  if (day>number_of_days()) +  if (!force) +  return 0; +  else +  return Day("ymd_jd",rules,jd+day-1,max(0,day_n-1))->autopromote(); +  return Day("ymd_jd",rules,jd+day-1,day_n)->autopromote(); +  } +  +  TimeRange place(TimeRange what,void|int force) +  { +  if (what->is_supertimerange) +  return what->mend_overlap(map(what->parts,place,force)); + // return `|(@map(what->parts,place,force)); +  +  if (what->is_year) +  return year()->place(what,force); // just fallback +  if (what->is_month) +  return month()->place(what,force); // just fallback +  +  if (what->is_week) +  return Week("ymd_yw",rules,wy,w,what->number_of_weeks()); +  +  if (what->is_day) +  return place_day(what->week_day(),what->n,force); +  +  if (what->is_timeofday) +  return place(what->day(),force)->place(what,force); +  +  error("place: Incompatible type %O\n",what); +  } +  + // --- Week to other units +  +  int number_of_years() +  { +  if (n<=1 && y == wy) return 1; +  +  [int y2,int w2,int wd2,int nd2,int jd2]=week_from_week(wy,w+n); +  return 1+y2-y; +  } +  +  int number_of_months() +  { +  if (!n) return 1; +  + // cheat +  return Day("ymd_jd",rules,jd,number_of_days()) +  ->number_of_months(); +  } +  +  int number_of_weeks() +  { +  return n; +  } +  +  int number_of_days(); +  + //! method Day day() + //! method Day day(int n) + //! method Day day(string name) + //! The Week type overloads the day() method, + //! so it is possible to get a specified weekday + //! by string: + //! + //! <tt>week-&gt;day("sunday")</tt> + //! + //! The integer and no argument behavior is inherited + //! from <ref to=YMD.day>YMD</ref>(). + //! + //! note: + //! the weekday-from-string routine is language dependent. +  +  cDay day(int|string ... mp) +  { +  if (sizeof(mp) && +  stringp(mp[0])) +  { +  int num=((int)mp[0]) || +  rules->language[f_week_day_number_from_name](mp[0]); +  if (!num) +  error("no such day %O in %O\n",mp[0],this); +  +  return ::day(num); +  } +  else +  return ::day(@mp); +  } +  +  cWeek set_ruleset(Calendar.Ruleset r) +  { +  return Week("ymd_yjwm",r,y,yjd,jd,wy,w,n,md,m,mnd); +  } +  + // --- needs to be defined +  +  protected int weeks_to_week(int y,int m); + } +  + // ---------------------------------------------------------------- + //! class Day + //! inherits YMD + // ---------------------------------------------------------------- +  + class cDay + { +  inherit YMD; +  +  constant is_day=1; +  int nw; +  + //! + //! method void create("unix",int unix_time) + //! method void create("julian",int|float julian_day) + //! method void create(int year,int month,int day) + //! method void create(int year,int year_day) + //! method void create(int julian_day) + //! It's possible to create the day + //! by using five different methods; either the normal + //! way - from standard unix time or the julian day, + //! and also, for more practical use, from year, month and day, + //! from year and day of year, and from julian day + //! without extra fuzz. +  +  protected void create(mixed ...args) +  { +  if (!sizeof(args)) +  { +  rules=default_rules; +  create_unixtime_default(time()); +  return; +  } +  else +  switch (args[0]) +  { +  case "ymd_ydmw": +  rules=args[1]; +  y=args[2]; +  yjd=args[3]; +  jd=args[4]; +  yd=args[5]; +  n=args[6]; +  m=args[7]; +  md=args[8]; +  wy=args[9]; +  w=args[10]; +  wd=args[11]; +  mnd=args[12]; +  nw=CALUNKNOWN; +  return; +  case "ymd_yd": +  rules=args[1]; +  y=args[2]; +  yjd=args[3]; +  jd=args[4]; +  yd=args[5]; +  n=args[6]; +  wy=wd=nw=md=m=w=CALUNKNOWN; +  return; +  case "ymd_jd": +  rules=args[1]; +  create_julian_day(args[2]); +  n=args[3]; +  wy=wd=nw=md=m=w=CALUNKNOWN; +  return; +  case "unix_r": +  case "julian_r": +  case "unix": +  case "julian": +  // Handled by ::create. +  break; +  default: +  rules=default_rules; +  wy=wd=nw=md=m=w=CALUNKNOWN; +  n=1; +  switch (sizeof(args)) +  { +  case 1: +  if (intp(args[0])) +  { +  create_julian_day(args[0]); +  return; +  } +  break; +  case 2: +  if (stringp(args[0])) +  y=default_rules->language[f_year_number_from_name] +  (args[0]); +  else if (intp(args[0])) +  y=args[0]; +  else +  break; +  if (!intp(args[1])) +  break; +  yd=args[1]; +  yjd=julian_day_from_year(y); +  jd=yjd+yd-1; +  return; +  case 3: +  if (stringp(args[0])) +  y=default_rules->language[f_year_number_from_name] +  (args[0]); +  else if (intp(args[0])) +  y=args[0]; +  else +  break; +  if (!intp(args[1]) || +  !intp(args[2])) break; +  md=args[2]; +  [y,m,int nmd,int myd]= +  year_month_from_month(y,args[1]); +  if (m!=args[1] || y!=args[0]) +  error("No such month (%d-%02d)\n",args[0],args[1]); +  yjd=julian_day_from_year(y); +  md=args[2]; +  jd=yjd+myd+md-2; +  yd=jd-yjd+1; +  if (md>nmd || md<1) +  error("No such day of month (%d-%02d-%02d)\n", +  @args); +  return; +  } +  } +  +  rules=default_rules; +  ::create(@args); +  } +  +  void create_julian_day(int|float _jd) +  { +  n=1; +  nw=md=m=wd=w=wy=CALUNKNOWN; // unknown +  +  if (floatp(_jd)) +  { +  create_unixtime_default((int)((jd-2440588)*86400)); +  } +  else +  { +  [y,yjd]=year_from_julian_day(jd=_jd); +  yd=1+jd-yjd; +  } +  } +  +  protected string _sprintf(int t,mapping m) +  { +  switch (t) +  { +  case 'O': +  catch { +  if (n!=1) +  return sprintf("Day(%s)",nice_print_period()); +  return sprintf("Day(%s)",nice_print()); +  }; +  return sprintf("Day(%d)", unix_time()); +  case 't': +  return "Calendar."+calendar_name()+".Day"; +  default: +  return ::_sprintf(t,m); +  } +  } +  +  string nice_print() +  { +  if (m==CALUNKNOWN) make_month(); +  if (wd==CALUNKNOWN) make_week(); +  return +  sprintf("%s %s %s %s", +  week_day_shortname(), +  month_day_name(),month_shortname(), +  year_name()); +  } +  +  string format_nice() +  { +  if (m==CALUNKNOWN) make_month(); +  if (calendar()->Year()!=year()) +  return +  sprintf("%s %s %s", +  month_day_name(),month_shortname(), +  year_name()); +  else +  return +  sprintf("%s %s", +  month_day_name(),month_shortname()); +  } +  +  string nice_print_period() +  { + // return nice_print()+" n="+n+""; +  if (!n) return nice_print()+" "+minute()->nice_print()+" sharp"; +  return nice_print()+" .. "+day(-1)->nice_print(); +  } +  +  TimeRange beginning() +  { +  return Day("ymd_ydmw",rules,y,yjd,jd,yd,0,m,md,wy,w,wd,mnd); +  } +  +  TimeRange end() +  { +  return Day("ymd_jd",rules,jd+n,0) +  ->autopromote(); +  } +  +  protected void convert_from(TimeRange other) +  { +  ::convert_from(other); +  if (other->number_of_days) +  n=other->number_of_days(); +  else +  n=0; +  } +  + // --- Day _move +  +  protected TimeRange _move(int x,YMD step) +  { +  if (step->is_year) { +  TimeRange stepped = year()->add(x,step); +  if (TimeRange placed = stepped->place(this,0)) +  return placed; +  // If we couldn't place our day in the target year it means +  // we're on a leap day and the target year doesn't have any. +  // We return the closest day in the same month. +  TimeRange placed = stepped->place (month()); +  if (md == CALUNKNOWN) make_month(); +  return placed->day (md < placed->number_of_days() ? md : -1); +  } +  +  if (step->is_month) { +  TimeRange stepped = month()->add(x,step); +  if (TimeRange placed = stepped->place(this,0)) +  return placed; +  // The target month is shorter and our date doesn't exist in +  // it. We return the closest (i.e. last) day of the target +  // month. +  return stepped->day (-1); +  } +  +  if (step->is_week) +  return week()->add(x,step)->place(this,1); +  +  if (step->is_day) +  return Day("ymd_jd",rules,jd+x*step->n,n) +  ->autopromote(); +  +  error("_move: Incompatible type %O\n",step); +  } +  +  TimeRange place(TimeRange what,int|void force) +  { +  if (what->is_timeofday) +  { +  int lux= +  what->ux- +  Day("unix_r",what->unix_time(),what->ruleset()) +  ->unix_time(); +  TimeRange res; +  +  if (what->is_timeofday_f) +  res= +  Fraction("timeofday_f",rules, +  lux+unix_time(),what->ns,what->s_len,what->ns_len); +  else +  res=Second("timeofday",rules,unix_time()+lux,what->len); +  +  if (what->rules->timezone->is_dst_timezone || +  rules->timezone->is_dst_timezone) +  { +  int u0=what->utc_offset()-what->day()->utc_offset(); +  int u1=res->utc_offset()-utc_offset(); + // werror("%O %O\n",u0,u1); +  if (u1-u0) +  res=res->add(u1-u0,Second); +  else +  res=res->autopromote(); +  +  if (!force) +  { +  if (res->hour_no()!=what->hour_no()) +  error("place: no such time of " +  "day (DST shift): %O\n", what); +  } +  } +  else +  res=res->autopromote(); +  +  return res; +  } +  +  if (what->is_year) +  return year()->place(what,force); // just fallback +  if (what->is_month) +  return month()->place(what,force); // just fallback +  if (what->is_week) +  return week()->place(what,force); // just fallback +  +  if (what->is_day) +  return Day("ymd_jd",rules,jd,what->number_of_days()) +  ->autopromote(); +  +  error("place: Incompatible type %O\n",what); +  } +  +  TimeRange distance(TimeRange to) +  { +  if (to->is_timeofday) +  return hour()->distance(to); +  if (to->is_ymd) +  { +  int d1=jd; +  int d2=to->jd; +  if (d2<d1) +  error("distance: negative distance (%d days)\n",d2-d1); +  return Day("ymd_ydmw",rules,y,yjd,jd,yd,d2-d1,m,md,wy,w,wd,mnd) +  ->autopromote(); +  } +  +  error("distance: Incompatible type %O\n",to); +  } +  + // --- Day to other YMD +  +  int number_of_days() +  { +  return n; +  } +  +  int number_of_years() +  { +  if (n<=1) return 1; +  return 1+year_from_julian_day(jd+n-1)[0]-y; +  } +  +  int number_of_weeks() +  { +  if (nw!=CALUNKNOWN) return nw; +  +  if (n<=1) return nw=1; +  +  return nw= +  Week("julian_r",jd,rules) +  ->range(Week("julian_r",jd+n-1,rules)) +  ->number_of_weeks(); +  } +  +  cDay set_ruleset(Calendar.Ruleset r) +  { +  return Day("ymd_ydmw",r,y,yjd,jd,yd,n,m,md,wy,w,wd,mnd); +  } +  + // backwards compatible with calendar I +  string iso_name() { return format_ymd(); } +  string iso_short_name() { return format_ymd_short(); } + } +  + function(mixed...:cDay) Day=cDay; +  + //------------------------------------------------------------------------ + //- class YMD_Time + //------------------------------------------------------------------------ +  + class YMD_Time + { + #define MKRBASE \ +  do \ +  { \ +  int n; \ +  if (!rbase) \ +  rbase=Day("unix_r",this->ux,this->rules)->range(Day("unix_r",this->ux+this->len,this->rules)); \ +  } while (0) +  + #define RBASE (this->base || this->make_base()) +  +  cDay day(int ...n) { return RBASE->day(@n); } +  cDay number_of_days() { return RBASE->number_of_days(); } +  array(cDay) days(int ...r) { return RBASE->days(@r); } +  +  cMonth month(int ...n) { return RBASE->month(@n); } +  cMonth number_of_months() { return RBASE->number_of_months(); } +  array(cMonth) months(int ...r) { return RBASE->months(@r); } +  +  cWeek week(int ...n) { return RBASE->week(@n); } +  cWeek number_of_weeks() { return RBASE->number_of_weeks(); } +  array(cWeek) weeks(int ...r) { return RBASE->weeks(@r); } +  +  cYear year(int ...n) { return RBASE->year(@n); } +  cYear number_of_years() { return RBASE->number_of_years(); } +  array(cYear) years(int ...r) { return RBASE->years(@r); } +  +  int year_no() { return RBASE->year_no(); } +  int month_no() { return RBASE->month_no(); } +  int week_no() { return RBASE->week_no(); } +  int month_name() { return RBASE->month_name(); } +  string month_shortname() { return RBASE->month_shortname(); } +  int month_day() { return RBASE->month_day(); } +  string month_day_name() { return RBASE->month_day_name(); } +  int week_day() { return RBASE->week_day(); } +  int year_day() { return RBASE->year_day(); } +  int year_name() { return RBASE->year_name(); } +  string week_name() { return RBASE->week_name(); } +  string week_day_name() { return RBASE->week_day_name(); } +  string week_day_shortname() { return RBASE->week_day_shortname(); } +  int leap_year() { return RBASE->leap_year(); } +  +  string format_iso_ymd() { return RBASE->format_iso_ymd(); } +  string format_ext_ymd() { return RBASE->format_ext_ymd(); } +  string format_ymd() { return RBASE->format_ymd(); } +  string format_ymd_short() { return RBASE->format_ymd_short(); } +  string format_ymd_xshort() { return RBASE->format_ymd_xshort(); } +  string format_iso_week() { return RBASE->format_iso_week(); } +  string format_iso_week_short() +  { return RBASE->format_iso_week_short(); } +  string format_week() { return RBASE->format_week(); } +  string format_week_short() { return RBASE->format_week_short(); } +  string format_month() { return RBASE->format_month(); } +  string format_month_short() { return RBASE->format_month_short(); } +  + #undef RBASE + } +  + #define OVERLOAD_TIMEOFDAY \ +  \ +  protected int(0..1) create_backtry(mixed ... args) \ +  { \ +  if (sizeof(args)>=5 && \ +  (intp(args[0])||stringp(args[0])) && \ +  intp(args[1]) && \ +  intp(args[2])) \ +  { \ +  base=Day(@args[..2]); \ +  return ::create_backtry(@args[3..]); \ +  } \ +  return ::create_backtry(@args); \ +  } +  +  + //------------------------------------------------------------------------ + //! class Hour + //! inherits Time.Hour + //! inherits YMD + //------------------------------------------------------------------------ +  + class cHour + { +  inherit Time::cHour; +  inherit YMD_Time; +  OVERLOAD_TIMEOFDAY; + } +  + //------------------------------------------------------------------------ + //! class Minute + //! inherits Time.Minute + //! inherits YMD + //------------------------------------------------------------------------ +  + class cMinute + { +  inherit Time::cMinute; +  inherit YMD_Time; +  OVERLOAD_TIMEOFDAY; + } +  + //------------------------------------------------------------------------ + //! class Second + //! inherits Time.Second + //! inherits YMD + //------------------------------------------------------------------------ +  + class cSecond + { +  inherit Time::cSecond; +  inherit YMD_Time; +  OVERLOAD_TIMEOFDAY; + } +  + //------------------------------------------------------------------------ + //! class Fraction + //! inherits Time.Fraction + //! inherits YMD + //------------------------------------------------------------------------ +  + class cFraction + { +  inherit Time::cFraction; +  inherit YMD_Time; +  OVERLOAD_TIMEOFDAY; + } +  + //------------------------------------------------------------------------ + //! class SuperTimeRange + //! inherits Time.SuperTimeRange + //------------------------------------------------------------------------ +  + class cSuperTimeRange + { +  inherit Time::cSuperTimeRange; +  +  array(cYear) years(int ...range) { return get_units("years",@range); } +  cYear year(void|int n) { return get_unit("years",n); } +  int number_of_years() { return num_units("years"); } +  +  array(cMonth) months(int ...range) { return get_units("months",@range); } +  cMonth month(void|int n) { return get_unit("months",n); } +  int number_of_months() { return num_units("months"); } +  +  array(cWeek) weeks(int ...range) { return get_units("weeks",@range); } +  cWeek week(void|int n) { return get_unit("weeks",n); } +  int number_of_weeks() { return num_units("weeks"); } +  +  array(cDay) days(int ...range) { return get_units("days",@range); } +  cDay day(void|int n) { return get_unit("days",n); } +  int number_of_days() { return num_units("days"); } +  + #define RBASE parts[0] +  +  int year_no() { return RBASE->year_no(); } +  int month_no() { return RBASE->month_no(); } +  int week_no() { return RBASE->week_no(); } +  int month_name() { return RBASE->month_name(); } +  string month_shortname() { return RBASE->month_shortname(); } +  int month_day() { return RBASE->month_day(); } +  string month_day_name() { return RBASE->month_day_name(); } +  int week_day() { return RBASE->week_day(); } +  int year_day() { return RBASE->year_day(); } +  string week_name() { return RBASE->week_name(); } +  string week_day_name() { return RBASE->week_day_name(); } +  string week_day_shortname() { return RBASE->week_day_shortname(); } +  int leap_year() { return RBASE->leap_year(); } +  +  string format_iso_ymd() { return RBASE->format_iso_ymd(); } +  string format_ext_ymd() { return RBASE->format_ext_ymd(); } +  string format_ymd() { return RBASE->format_ymd(); } +  string format_ymd_short() { return RBASE->format_ymd_short(); } +  string format_ymd_xshort() { return RBASE->format_ymd_xshort(); } +  string format_iso_week() { return RBASE->format_iso_week(); } +  string format_iso_week_short() { return RBASE->format_iso_week_short(); } +  string format_week() { return RBASE->format_week(); } +  string format_week_short() { return RBASE->format_week_short(); } +  string format_month() { return RBASE->format_month(); } +  string format_month_short() { return RBASE->format_month_short(); } +  + #undef RBASE + } +  + //------------------------------------------------------------------------ + // Pop out doc-extractor context to the top-level scope. + //! module Calendar + //! submodule YMD + // global convenience functions + //------------------------------------------------------------------------ +  + //! method TimeRange parse(string fmt,string arg) + //! parse a date, create relevant object + //! fmt is in the format "abc%xdef..." + //! where abc and def is matched, and %x is + //! one of those time units: + //! <pre> + //! %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) + //! %S seconds since the Epoch (only combines with %f) + //! %f fraction of a second (needs %s or %S) + //! %t short time (205314, 2053) + //! %z zone + //! %p "am" or "pm" + //! %n empty string (to be put at the end of formats) + //! </pre> + //! + //! returns 0 if format doesn't match data, or the appropriate time object. + //! + //! note: + //! The zone will be a guess if it doesn't state an exact + //! regional timezone (like "Europe/Stockholm") - + //! most zone abbriviations (like "CET") are used by more + //! then one region with it's own daylight saving rules. + //! Also beware that for instance CST can be up to four different zones, + //! central Australia or America being the most common. + //! + //! <pre> + //! Abbreviation Interpretation + //! AMT America/Manaus [UTC-4] + //! AST America/Curacao [UTC-4] + //! CDT America/Costa_Rica [UTC-6] + //! CST America/El Salvador [UTC-6] + //! EST America/Panama [UTC-5] + //! GST Asia/Dubai [UTC+4] + //! IST Asia/Jerusalem [UTC+2] + //! WST Australia/Perth [UTC+8] + //! </pre> + //! + //! This mapping is modifiable in the ruleset, see + //! <ref>Ruleset.set_abbr2zone</ref>. +  +  + // dwim time of day; needed to correct timezones + // this API may change without further notice + protected TimeRange dwim_tod(TimeRange origin,string whut,int h,int m,int s) + { +  TimeRange tr; +  if (catch { +  tr=origin[whut](h,m,s); +  }) { +  if (h==24 && m==0 && s==0) // special case +  return origin->end()->second(); +  else { +  object d=origin->day(); +  array(cHour) ha=origin->hours(); +  int n=search(ha->hour_no(),h); +  if (n!=-1) tr=ha[n]->minute(m)->second(s); +  else return 0; // no such hour +  } +  } +  +  if (tr->hour_no()!=h || tr->minute_no()!=m) +  { + // werror("%O %O %O -> %O %O %O (%O)\n", + // tr->hour_no(),tr->minute_no(),tr->second_no(), + // h,m,s,tr); +  if (tr->hour_no()!=h) +  tr=tr->add(h-tr->hour_no(),Hour); +  if (tr->minute_no()!=m) +  tr=tr->add(m-tr->minute_no(),Minute); +  if (tr->second_no()!=s) +  tr=tr->add(s-tr->second_no(),Second); +  if (tr->hour_no()!=h || tr->minute_no()!=m || +  tr->second_no()!=s) return 0; // no such hour +  } +  return tr; + } +  + protected mapping abbr2zones; +  + // dwim timezone and call dwim time of day above + // this API may change without further notice + protected TimeRange dwim_zone(TimeRange origin,string zonename, +  string whut,int ...args) + { +  if (zonename=="") return 0; +  +  if (zonename[0]=='"') sscanf(zonename,"\"%s\"",zonename); +  sscanf(zonename,"%*[ \t]%s",zonename); +  +  if(sizeof(zonename)==4 && zonename[2]=='S') +  zonename = zonename[0..1] + zonename[3..3]; +  else if(sizeof(zonename)>4 && has_suffix(zonename, "DST")) +  zonename = zonename[..<3]; +  +  if (origin->rules->abbr2zone[zonename]) +  zonename=origin->rules->abbr2zone[zonename]; +  +  Calendar.Rule.Timezone zone = Calendar.Timezone[zonename]; +  +  if (!zone) +  { +  if (sscanf(zonename,"%[^+-]%s",string a,string b)==2 && a!="" && b!="") +  { +  TimeRange tr=dwim_zone(origin,a,whut,@args); +  if (!tr) return 0; +  +  return +  dwim_tod(origin->set_timezone( +  Calendar.Timezone.make_new_timezone( +  tr->timezone(), +  Calendar.Timezone.decode_timeskew(b))), +  whut,@args); +  } +  if(!abbr2zones) +  abbr2zones = master()->resolv("Calendar")["TZnames"]["abbr2zones"]; +  array pz=abbr2zones[zonename]; +  if (!pz) return 0; +  foreach (pz,string zn) +  { +  TimeRange try=dwim_zone(origin,zn,whut,@args); +  if (try && try->tzname()==zonename) return try; +  } +  return 0; +  } +  +  return dwim_tod(origin->set_timezone(zone),whut,@args); + } +  + protected mapping(string:array) parse_format_cache=([]); +  + protected mapping dwim_year=([ "past_lower":70, "past_upper":100, +  "current_century":2000, "past_century":1900 ]); +  + TimeRange parse(string fmt,string arg,void|TimeRange context) + { +  [string nfmt,array q]=(parse_format_cache[fmt]||({0,0})); +  +  if (!nfmt) +  { + // nfmt=replace(fmt," %","%*[ \t]%"); // whitespace -> whitespace + #define ALNU "%[^ -,./:-?[-`{-¿-]" + #define AMPM "%[ampAMP.]" + #define NUME "%[0-9]" + #define ZONE "%[-+0-9A-Za-z/]" +  nfmt=replace(fmt, +  ({"%Y","%y","%M","%W","%D","%a","%e","%h","%m","%s","%p", +  "%t","%f","%d","%z","%n","%S"}), +  ({ALNU,ALNU,ALNU,"%d",NUME,"%d",ALNU,"%d","%d","%d",AMPM, +  NUME,NUME,NUME,ZONE,"%s","%d"})); +  + #if 1 +  q=array_sscanf(fmt,"%{%*[^%]%%%1s%}")*({})*({})-({"*","%"}); + #else + // slower alternatives: +  array q=Array.map(replace(fmt,({"%*","%%"}),({"",""}))/"%", +  lambda(string s){ return s[..0];})-({""}); +  +  array q=({}); +  array pr=(array)fmt; +  int i=-1; +  while ((i=search(pr,'%',i+1))!=-1) q+=({sprintf("%c",pr[i+1])}); + #endif +  if (sizeof(q)==0) error("format doesn't contain anything to parse\n"); +  if (q[-1]=="z") nfmt=replace(nfmt,ZONE,"%s"); +  parse_format_cache[fmt]=({nfmt,q}); +  } +  +  array res=array_sscanf(arg,nfmt); +  +  int i=search(res,""); +  if (i!=-1 && i<sizeof(res)-1) return 0; +  +  if (sizeof(res)<sizeof(q)) +  return 0; // parse error +  +  mapping m=mkmapping(q,res); +  if (i!=-1 && m->n!="") return 0; +  + // werror("%O\n",m); +  + // werror("bopa %O\n %O\n %O\n %O\n",fmt,arg,nfmt,m); +  +  +  TimeRange low; +  +  Calendar.Calendar cal=this; +  +  + // #define NOCATCH + #ifndef NOCATCH +  if (catch { + #else +  werror("%O\n",m); + #endif +  if (m->n && m->n!="") return 0; +  +  if (m->Y) +  m->Y=default_rules->language[f_year_number_from_name](m->Y); +  +  if (!zero_type(m->Y) && m->D && (int)m->M) +  low=m->day=cal->Day(m->Y,(int)m->M,(int)m->D); +  +  +  if (m->d) +  { +  int y,mo,d; +  +  if (sizeof(m->d)==6) +  { +  [y,mo,d]=(array(int))(m->d/2); +  if (y<dwim_year->past_lower) +  y+=dwim_year->current_century; +  else +  y+=dwim_year->past_century; +  } +  else if (sizeof(m->d)==8) +  [y,mo,d]=(array(int))array_sscanf(m->d,"%4s%2s%2s"); +  else return 0; +  +  low=m->day=cal->Day(y,mo,d); +  } +  else +  { +  if (!zero_type(m->Y)) m->year=cal->Year(m->Y); +  else if (m->y) +  { +  if (sizeof(m->y)<3) +  { +  m->y=(int)m->y; +  // FIXME? these should be adjustable for different calendars. +  if (m->y<dwim_year->past_lower) +  m->y+=dwim_year->current_century; +  else if (m->y<dwim_year->past_upper) +  m->y+=dwim_year->past_century; +  } +  low=m->year=cal->Year(m->y); +  } +  else low=m->year=context?context->year():cal->Year(); +  +  if (m->M) +  { +  m->month=low=m->year->month(m->M); +  } +  if (m->W) +  m->week=low=m->year->week("w"+m->W); +  +  if (!zero_type(m->D)) +  m->day=low=(m->month||(context?context->month():cal->Month())) +  ->day((int)m->D); +  else if (!zero_type(m->a)) +  m->day=low=(m->month || m->year)->day(m->a); +  else if (!zero_type(m->e)) +  m->day=low=(m->week||(context?context->week():cal->Week())) +  ->day(m->e); +  else +  low=m->day=context?context->day():cal->Day(); +  +  if (m->day && zero_type(m->Y) && zero_type(m->y) && m->e) +  if (m->month) +  { +  // scan for closest year that matches +  cYear y2=m->day->year(); +  object d2; +  int i; +  for (i=0; i<20; i++) +  { +  d2=(y2+i)->place(m->day); +  if (d2 && d2->week()->day(m->e)==d2) break; +  d2=(y2-i)->place(m->day); +  if (d2 && d2->week()->day(m->e)==d2) break; +  } +  if (i==20) return 0; +  low=m->day=d2; +  } +  else +  { +  // scan for closest month that matches +  cYear m2=m->day->month(); +  object d2; +  int i; +  for (i=0; i<20; i++) +  { +  d2=(m2+i)->place(m->day); +  if (d2 && d2->week()->day(m->e)==d2) break; +  d2=(m2-i)->place(m->day); +  if (d2 && d2->week()->day(m->e)==d2) break; +  } +  if (i==20) return 0; +  low=m->day=d2; +  } +  } +  +  int h=0,mi=0,s=0; +  float sub_second; +  string g=0; +  +  if (m->t) +  { +  if (sizeof(m->t)==6) +  [h,mi,s]=(array(int))(m->t/2),g="second"; +  else if (sizeof(m->t)==4) +  [h,mi]=(array(int))(m->t/2),g="minute"; +  else return 0; +  } +  else +  { +  if (!zero_type(m->h)) h=m->h,g="hour"; +  if (!zero_type(m->m)) mi=m->m,g="minute"; +  if (!zero_type(m->s)) s=m->s,g="second"; +  if (!zero_type(m->f)) sub_second=(float)("0."+m->f+"0"*9)[..10]; +  } +  +  if (!zero_type(m->p)) +  { +  switch (lower_case(m->p)-".") +  { +  case "am": +  if (h==12) h=0; +  break; +  case "pm": +  if (h!=12) h+=12; +  break; +  default: +  return 0; // need "am" or "pm" +  } +  } +  if (m->z) // zone +  low = dwim_zone(low,m->z,g,h,mi,s); +  else if (g) +  low = dwim_tod(low,g,h,mi,s); +  else if (!zero_type(m->S)) +  low = Second(m->S); +  if (sub_second) +  low = low->fraction(sub_second); +  return low; +  + #ifndef NOCATCH +  }) + #endif +  return 0; + } +  + //! method Day dwim_day(string date) + //! method Day dwim_day(string date,TimeRange context) + //! Tries a number of different formats on the given date (in order): + //! <pre> + //! <ref>parse</ref> format as in + //! "%y-%M-%D (%M) -W%W-%e (%e)" "2000-03-20 (Mar) -W12-1 (Mon)" + //! "%y-%M-%D" "2000-03-20", "00-03-20" + //! "%M%/%D/%y" "3/20/2000" + //! "%D%*[ /]%M%*[ /-,]%y" "20/3/2000" "20 mar 2000" "20/3 -00" + //! "%e%*[ ]%D%*[ /]%M%*[ /-,]%y" "Mon 20 Mar 2000" "Mon 20/3 2000" + //! "-%y%*[ /]%D%*[ /]%M" "-00 20/3" "-00 20 mar" + //! "-%y%*[ /]%M%*[ /]%D" "-00 3/20" "-00 march 20" + //! "%y%*[ /]%D%*[ /]%M" "00 20 mar" "2000 20/3" + //! "%y%*[ /]%M%*[ /]%D" "2000 march 20" + //! "%D%.%M.%y" "20.3.2000" + //! "%D%*[ -/]%M" "20/3" "20 mar" "20-03" + //! "%M%*[ -/]%D" "3/20" "march 20" + //! "%M-%D-%y" "03-20-2000" + //! "%D-%M-%y" "20-03-2000" + //! "%e%*[- /]%D%*[- /]%M" "mon 20 march" + //! "%e%*[- /]%M%*[- /]%D" "mon/march/20" + //! "%e%*[ -/wv]%W%*[ -/]%y" "mon w12 -00" "1 w12 2000" + //! "%e%*[ -/wv]%W" "mon w12" + //! "%d" "20000320", "000320" + //! "today" "today" + //! "last %e" "last monday" + //! "next %e" "next monday" + //! </pre> + //! + //! note: + //! Casts exception if it fails to dwim out a day. + //! "dwim" means do-what-i-mean. +  + /* tests: +  + Calendar.dwim_day("2000-03-20 (Mar) -W12-1 (Mon)"); + Calendar.dwim_day("20/3/2000"); + Calendar.dwim_day("20 mar 2000"); + Calendar.dwim_day("20/3 -00"); + Calendar.dwim_day("Mon 20 Mar 2000" ); + Calendar.dwim_day("Mon 20/3 2000"); + Calendar.dwim_day("2000-03-20"); + Calendar.dwim_day("00-03-20"); + Calendar.dwim_day("20000320"); + Calendar.dwim_day("000320"); + Calendar.dwim_day("-00 20/3" ); + Calendar.dwim_day("-00 20 mar"); + Calendar.dwim_day("-00 3/20" ); + Calendar.dwim_day("-00 march 20"); + Calendar.dwim_day("00 20 mar" ); + Calendar.dwim_day("2000 20/3"); + Calendar.dwim_day("2000 march 20"); + Calendar.dwim_day("20/3" ); + Calendar.dwim_day("20 mar" ); + Calendar.dwim_day("20-03"); + Calendar.dwim_day("3/20" ); + Calendar.dwim_day("march 20"); + Calendar.dwim_day("mon w12 -00" ); + Calendar.dwim_day("1 w12 2000"); + Calendar.dwim_day("mon w12"); + Calendar.dwim_day("monday" ); + Calendar.dwim_day("1"); + Calendar.dwim_day("today"); + Calendar.dwim_day("last monday"); + Calendar.dwim_day("next monday"); + Calendar.dwim_day("Sat Jun 2"); +  + */ +  + protected constant dwim_day_strings= + ({ +  "%y-%M-%D (%*s) -W%W-%e (%e)", +  "%y-%M-%D", +  "%M/%D/%y", +  "%D%*[ /]%M%*[- /,]%y", +  "%M %D%*[- /,]%y", +  "%e%*[, ]%D%*[a-z:]%*[ /]%M%*[-/ ,]%y", +  "%e%*[, ]%M%*[ ,]%D%*[ ,]%y", +  "-%y%*[ /]%D%*[ /]%M", +  "-%y%*[ /]%M%*[ /]%D", +  "%y%*[ /]%M%*[ /]%D", +  "%y%*[ /]%D%*[ /]%M", +  "%D.%M.%y", +  "%D%*[- /]%M", +  "%M%*[- /]%D", +  "%M-%D-%y", +  "%D-%M-%y", +  "%e%*[- /]%D%*[- /]%M", +  "%e%*[- /]%M%*[- /]%D", +  "%e%*[- /wv]%W%*[ -/]%y", +  "%e%*[- /wv]%W", +  "%d" + }); +  + cDay dwim_day(string day,void|TimeRange context) + { +  cDay d; +  +  foreach ( dwim_day_strings, +  string dayformat) +  if ( (d=parse(dayformat+"%n",day,context)) ) +  return d; +  +  cDay t=context?context->day():Day(); +  if ( (d=parse("%e",day,context)) ) +  { +  if (d>=t) return d; +  else return (d->week()+1)->place(d); +  } +  +  if (sizeof(day)==4) +  catch { +  d = parse("%M/%D",day/2*"/",context); +  if(d) return d; +  }; +  +  if (day=="today") return t; +  if (day=="tomorrow") return t+1; +  if (day=="yesterday") return t-1; +  if (sscanf(day,"last %s",day)) +  { +  cDay d=dwim_day(day); +  return (d->week()-1)->place(d); +  } +  if (sscanf(day,"next %s",day)) +  { +  cDay d=dwim_day(day); +  return (d->week()+1)->place(d); +  } +  +  error("Failed to dwim day from %O\n",day); + } +  + //! method Day dwim_time(string date_time) + //! method Day dwim_time(string date_time, TimeRange context) + //! Tries a number of different formats on the given date_time. + //! + //! note: + //! Casts exception if it fails to dwim out a time. + //! "dwim" means do-what-i-mean. +  + TimeofDay dwim_time(string what,void|TimeRange cx) + { +  TimeofDay t; +  + // #define COLON "$*[ :]" + #define COLON ":" + #define SPACED(X) replace(X," ","%*[ ]") +  +  what = String.trim_all_whites(what); +  +  if (sizeof(what)>22 && +  (t=http_time(what,cx))) return t; +  if (sizeof(what)>12 && +  (t=parse(SPACED("%e %M %D %h:%m:%s %Y"),what,cx))) return t; // ctime +  if (sizeof(what)>15 && +  (t=parse(SPACED("%e %M %D %h:%m:%s %z %Y"),what,cx))) return t; +  if (sizeof(what)>19 && +  (t=parse(SPACED("%e %M %D %h:%m:%s %z DST %Y"),what,cx))) return t; +  +  foreach ( dwim_day_strings + +  ({""}), +  string dayformat ) +  foreach ( ({ "%t %z", +  "T%t %z", +  "T%t", +  "%h"COLON"%m"COLON"%s %p %z", +  "%h"COLON"%m"COLON"%s %p", +  "%h"COLON"%m"COLON"%s %z", +  "%h"COLON"%m"COLON"%s%z", +  "%h"COLON"%m"COLON"%s", +  "%h"COLON"%m %p %z", +  "%h"COLON"%m %p", +  "%h"COLON"%m %z", +  "%h"COLON"%m%z", +  "%h"COLON"%m", +  "%h%*[ ]%p", +  "%*[a-zA-Z.] %h"COLON"%m"COLON"%s %p %z", +  "%*[a-zA-Z.] %h"COLON"%m"COLON"%s %p", +  "%*[a-zA-Z.] %h"COLON"%m"COLON"%s %z", +  "%*[a-zA-Z.] %h"COLON"%m"COLON"%s%z", +  "%*[a-zA-Z.] %h"COLON"%m"COLON"%s", +  "%*[a-zA-Z.] %h"COLON"%m %p %z", +  "%*[a-zA-Z.] %h"COLON"%m %p", +  "%*[a-zA-Z.] %h"COLON"%m %z", +  "%*[a-zA-Z.] %h"COLON"%m%z", +  "%*[a-zA-Z.] %h"COLON"%m", +  "%*[a-zA-Z.] %h%*[ ]%p", }), +  string todformat ) +  { + // werror("try: %O\n %O\n", + // dayformat+"%*[ ,]"+todformat, + // todformat+"%*[ ,]"+dayformat); +  if (dayformat=="") +  { +  if ( (t=parse(todformat+"%*[ ]%n",what,cx)) ) return t; +  } +  else +  { +  if ( (t=parse(dayformat+"%*[ ,:T]"+todformat,what,cx)) ) return t; +  if ( (t=parse(todformat+"%*[ ,:]"+dayformat,what,cx)) ) return t; +  } +  } +  +  error("Failed to dwim time from %O\n",what); + } +  + // Parses time according to HTTP 1.1 (RFC 2616) HTTP-date token. + TimeofDay http_time(string what, void|TimeRange cx) + { +  TimeofDay t; +  +  constant date1 = "%D %M %Y"; // 2+1+3+1+4=11 +  constant date2 = "%D-%M-%y"; // 2+1+3+1+2=9 +  constant date3 = "%M %*[ ]%D"; // 2+1+2=5 +  constant time = "%h:%m:%s"; // 2+1+2+1+2=8 +  +  // 3+2+ 11 +1+ 8 +4 = 29 +  // RFC 1123 (and RFC 822 which it bases its timestamp format on) +  // supports more variations than we support here. +  constant rfc1123_date = "%e, "+date1+" "+time+" %z"; +  +  // 6+2+ 9 +1+ 8 +4 = 33 +  constant rfc850_date = "%e, "+date2+" "+time+" %z"; +  +  // 3+1+ 5 +1+ 8 +1+4 = 23 +  constant asctime_date = "%e "+date3+" "+time+" %Y"; +  +  if( sizeof(what)<23 ) return 0; +  +  if( (t=parse(rfc1123_date, what, cx)) || +  (t=parse(rfc850_date, what, cx)) || +  (t=parse(asctime_date+" %z", what+" GMT", cx)) ) +  return t; +  +  return 0; + } +  + //-- auxillary functions------------------------------------------------ +  + //! + //! function mapping(string:int) datetime(int|void unix_time) + //! Replacement for localtime; gives back a mapping: + //! <pre> + //! ([ "year": int // year number (2000 AD=2000, 1 BC==0) + //! "month": int(1..) // month of year + //! "day": int(1..) // day of month + //! "yearday": int(1..) // day of year + //! "week": int(1..) // week of year + //! "week_day": int(1..) // day of week (depending on calendar) + //! "unix": int // unix time + //! "julian": float // julian day + //! "hour": int(0..) // hour of day, including dst + //! "minute": int(0..59) // minute of hour + //! "second": int(0..59) // second of minute + //! "fraction": float // fraction of second + //! "timezone": int // offset to utc, including dst + //! ]); + //! </pre> + //! This is the same as calling <ref>Second</ref>()-><ref to=Second.datetime>datetime</ref>(). + //! + //! function string datetime_name(int|void unix_time) + //! function string datetime_short_name(int|void unix_time) + //! Compat functions; same as <ref>format_iso</ref> + //! and <ref>format_iso_short</ref>. + //! + //! function string format_iso(void|int unix_time) + //! function string format_iso_short(void|int unix_time) + //! function string format_iso_tod(void|int unix_time) + //! function string format_day_iso(void|int unix_time) + //! function string format_day_iso_short(void|int unix_time) + //! Format the object into nice strings; + //! <pre> + //! iso "2000-06-02 (Jun) -W22-5 (Fri) 11:57:18 CEST" + //! iso_short "2000-06-02 11:57:18" + //! iso_tod "11:57:18" + //! </pre> +  + // Sane replacement for localtime(). + mapping(string:int) datetime(int|void unix_time) + { +  return Second("unix",unix_time||time())->datetime(); + } +  + string datetime_name(int|void unix_time) + { +  return Second("unix",unix_time||time())->format_iso(); + } +  + string datetime_short_name(int|void unix_time) + { +  return Second("unix",unix_time||time())->format_iso_short(); + } +  + string format_iso(int|void unix_time) + { +  return Second("unix",unix_time||time())->format_iso(); + } +  + string format_iso_short(int|void unix_time) + { +  return Second("unix",unix_time||time())->format_iso_short(); + } +  + string format_iso_tod(int|void unix_time) + { +  return Second("unix",unix_time||time())->format_iso_tod(); + } +  + string format_day_iso(int|void unix_time) + { +  return Day("unix",unix_time||time())->format_iso(); + } +  + string format_day_iso_short(int|void unix_time) + { +  return Day("unix",unix_time||time())->format_iso_short(); + }   Newline at end of file added.