After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 344005 - Add a class for supporting multiple, non-Gregorian calendar formats
Add a class for supporting multiple, non-Gregorian calendar formats
Status: RESOLVED OBSOLETE
Product: glib
Classification: Platform
Component: datetime
unspecified
Other All
: Normal enhancement
: ---
Assigned To: gtkdev
gtkdev
: 391626 632194 710768 (view as bug list)
Depends on:
Blocks: 114245 145382 273669 Persian Arabic 530668 710768
 
 
Reported: 2006-06-06 12:43 UTC by Roozbeh Pournader
Modified: 2018-05-24 10:49 UTC
See Also:
GNOME target: ---
GNOME version: Unversioned Enhancement


Attachments
suggested prototype gcalendar.h (14.00 KB, text/x-chdr)
2006-06-06 12:54 UTC, Roozbeh Pournader
Details
updated prototype (14.53 KB, text/plain)
2006-06-17 17:13 UTC, Roozbeh Pournader
Details
gdatetime/gcalendar idea (22.96 KB, application/x-gzip)
2009-10-29 06:06 UTC, Christian Hergert
Details

Description Roozbeh Pournader 2006-06-06 12:43:20 UTC
glib needs to support non-Gregorian calendars. This is specially needed for countries that use a non-Gregorian calendar by default, like the Hijri/Islamic calendar in some Middle Eastern countries and the Persian calendar in Iran and Afghanistan. See Unicode Technical Standard #35 (http://www.unicode.org/reports/tr35/) for more details. For a list of countries that use the non-Gregorian calendars, see:

http://unicode.org/cldr/repository/common/supplemental/supplementalData.xml?rev=HEAD&content-type=text/vnd.viewcvs-markup

(search for calendarData).

I will attach a prototype for discussion.
Comment 1 Roozbeh Pournader 2006-06-06 12:54:48 UTC
Created attachment 66822 [details]
suggested prototype gcalendar.h
Comment 2 Petr Tomasek 2006-06-06 13:48:53 UTC
Years ago I created (for my personal needs) small library that converts between gregorian, julian, islamic (civil), and hebrew dates. Feel free to use it for whatever you need...

http://www.etf.cuni.cz/~tomasek/pub/my/libkal-0.9.0.tar.gz
Comment 3 Roozbeh Pournader 2006-06-17 17:13:44 UTC
Created attachment 67540 [details]
updated prototype

Changes:
* Add a private pointer to GIntlDate for keeping unforeseen information
* Use *_set_time_t and *_set_time_val instead of deprecated *_set_time
* No need to make 'struct tm' known explicitly, as gdate.h now includes time.h
* Some calendars may need a day zero, so move BAD_DAY from 0 to MAXUINT
* Add a few comments
Comment 4 Matthias Clasen 2006-06-18 17:49:58 UTC
Hmm, it is a bit unfortunate to duplicate all of the GDate api as 
g_intl_date_. I haven't looked in detail at gdate.h though to convince
myself that it can't be extended to support "bizarre" calendars.
Comment 5 Daniel Holbach 2006-10-31 16:38:30 UTC
We had a similar request in https://launchpad.net/distros/ubuntu/+source/glib2.0/+bug/62589
Comment 6 Behdad Esfahbod 2007-01-03 09:17:45 UTC
*** Bug 391626 has been marked as a duplicate of this bug. ***
Comment 7 Behdad Esfahbod 2007-01-03 09:19:39 UTC
From the dupped bug:

Opened by Javad Nejati (reporter, points: 1)
2007-01-01 20:00 UTC [reply]

Multicalendar library patch for glib
please have a look at http://sourceforge.net/projects/multicalendar/ and
specially
http://sourceforge.net/project/showfiles.php?group_id=178321

Comment 8 Roozbeh Pournader 2007-01-03 15:05:45 UTC
(In reply to comment #7)
> Multicalendar library patch for glib
> please have a look at http://sourceforge.net/projects/multicalendar/ and
> specially
> http://sourceforge.net/project/showfiles.php?group_id=178321

I smell a new derivative of the good old copyvio code in the "multicalendar" thing (there should also be copyvios in the hijri code), and since the code quality is not good and the calendar algorithms are wrong (uses Birashk/Behrooz for Persian calendar, for example), I don't think it's worth a look really. 
Comment 9 Behdad Esfahbod 2007-01-03 18:48:32 UTC
I don't think we want to make glib depend on another library anyway, so it's kinda moot.
Comment 10 Javad Nejati 2007-01-04 23:18:42 UTC
Dear Roozbeh,

I expect you not to judge the code before even looking at it.
this library is not restricted to a single algorithm for each calendar. you can add as much as algorithm you like(via the plugin system).Also all the code is new and nothig copyvious regarding the algorithms!
BTW thank you behdad for your technical point.you mean you never consider such an external lbrary to use?
Comment 11 Roozbeh Pournader 2007-01-06 13:28:53 UTC
(In reply to comment #10)
> I expect you not to judge the code before even looking at it.

I looked at the code. I was suggesting that it was perhaps not worth the time of the other developers to look at the code. Behdad basically said that the architecture is also inappropriate for using in glib/gtk+.

> Also all the code is new and nothig copyvious regarding the algorithms!

I won't get into details here, as it's not related to the matter. I believe I see copyvios there and I can get into details if needed, but I am not very interested in pursuing every copyvio case of calendar codes anymore unless the violators plan to include it upstream. As there is not much chance of the code getting into GNOME, I am not interested in an unfruitful debate.
Comment 12 Behdad Esfahbod 2009-05-11 22:53:09 UTC
Comment on attachment 67540 [details]
updated prototype

Ok, finally got to review this.


>/* This implements a simplified calendar, for day-to-day
>   uses. GDate compatibility is preserved as much as possible, 
>   
>   The code will not be stand-alone, and for some calendars (e.g. Islamic)
>   will require an external table loaded at runtime.
>*/   
>
>
>#ifndef __G_INTL_DATE_H__
>#define __G_INTL_DATE_H__

That and the file name should be gcalendar perhaps?

>#include <glib/gdate.h>
>
>typedef guint32 GCalendarVersion;
>
>typedef struct _GCalendar GCalendar;
>
>typedef enum
>{
>  G_CALENDAR_GREGORIAN = 0,
>  G_CALENDAR_BUDDHIST,
>  G_CALENDAR_CHINESE, /* The default format used in CLDR is weird: investigate. This is probably the hardest calendar to implement, after Islamic. */
>  G_CALENDAR_COPTIC,
>  G_CALENDAR_ETHIOPIC,
>  G_CALENDAR_HEBREW,
>  G_CALENDAR_ISLAMIC, /* The observational/astronomical lunar calendar */
>  G_CALENDAR_ISLAMIC_CIVIL, /* The arithmetic lunar calendar */
>  G_CALENDAR_JAPANESE, /* Not the lunisolar calendar, but gregorian with emperor eras */
>  G_CALENDAR_PERSIAN,
>} GCalendarType;
>
>struct _GCalendar
>{
>  GCalendarType calendar_type;
>  const gchar  *territory; /* ISO 8601 or UN M.49 code, possibly appended by subterritory codes */
>  gpointer      priv; /* for keeping other differences like the islamic calendar's location */
>};

I'd rather we use an opaque, gconv-style constructor, for this.  That is, something like:

  g_calendar_new (const char *name);

Though if we do that, we also would need an enumeration API.  Another option is to keep it like you suggest, but either discourage, or make it impossible, to use the enum values and have people go through the genum API for enumerability.

The whole point being, when you install a new glib with more calendar types, you should NOT need to recompile evolution.

Does CLDR introduce a string tag for the calendar system plus all the properties needed (location, territory, timezone, etc)?  If yes, we should try parsing that in a constructor like the one I suggested above.


>GCalendar*       g_calendar_new           (void);
>/* Territory will be set to the calendar type's default territory */
>GCalendar*       g_calendar_new_type      (GCalendarType     calendar_type);
>/* Type will be set to territory's default calendar type, territory will be normalized, g_strdup-ed if not changing */
>GCalendar*       g_calendar_new_territory (const gchar      *territory);
>GCalendar*       g_calendar_new_full      (GCalendarType     calendar_type,
>                                           const gchar      *territory);
>void             g_calendar_free          (GCalendar        *calendar);
>
>void             g_calendar_clear         (GCalendar        *calendar,
>                                           guint             n_calendars);

With an opaque calendar, no _clear is needed.

>void             g_calendar_set_type      (GCalendar        *calendar,
>                                           GCalendarType     calendar_type);
>/* territory will be normalized, g_strdup-ed if not changing */
>void             g_calendar_set_territory (GCalendar        *calendar,
>                                           const gchar      *territory);
>void             g_calendar_set_full      (GCalendar        *calendar,
>                                           GCalendarType     calendar_type,
>                                           const gchar      *territory);

Do we actually need the object properties to be settable?  What's wrong with providing them all at creation time?  It's really like an iconv converter type of object to me.


>/* The information set by this will be used only if the calendar is localation-dependant (like Islamic) and the
> * territory does not have a national observance practice. If not set, the territory's
> * default location or its most populus location is used.
> *  
> *   latitude: angular distance from equator, in degrees. positive for northern hemisphere.
> *   longiture: angular distance from the Greenwich meridian, in degrees. positive for eastern hemisphere.
> *   elevation: elevation above sea level, in meters.
> *   timezone:  Olson timezone ID, like "Asia/Tokyo". Will be g_strdup-ed. Can be NULL.
> *
> * We should also probably define a constant for converstion between meters and feet.
> */
>void             g_calendar_set_location  (GCalendar       *calendar,
>                                           gfloat           latitude,
>                                           gfloat           longitude,
>                                           gfloat           elevation,
>                                           const gchar     *timezone);
>void             g_calendar_set_latitude  (GCalendar       *calendar,
>                                           gfloat           latitude);
>void             g_calendar_set_longitude (GCalendar       *calendar,
>                                           gfloat           longitude);
>void             g_calendar_set_elevation (GCalendar       *calendar,
>                                           gfloat           elevation);
>void             g_calendar_set_timezone  (GCalendar       *calendar,
>                                           const gchar     *timezone);

No gfloat please.  gdouble.

Also, why is the timezone relevant again?
The location stuff should perhaps wait till a later version.  But good to have them in mind during the design phase.


>#define G_METER_PER_FOOT 0.3048
>
>gboolean         g_calendar_valid_type    (GCalendarType    calendar_type) G_GNUC_CONST;

Definitely not the right name.

>GCalendarType    g_calendar_get_type      (const GCalendar *calendar);

This name is reserved for the GObject boxed type around this thing.  Perhaps use "system" instead of "type".

>const gchar*     g_calendar_get_territory (const GCalendar *calendar);
>/* The output of these four functions is undefined if the value is not explicitly set.
>   Different values from those set may be returned if the values are not used */
>gfloat           g_calendar_get_latitude  (const GCalendar *calendar);
>gfloat           g_calendar_get_longitude (const GCalendar *calendar);
>gfloat           g_calendar_get_elevation (const GCalendar *calendar);
>const gchar*     g_calendar_get_timezone  (const GCalendar *timezone);
>
>/* Localized name of calendar */
>const gchar*     g_calendar_get_name      (GCalendarType    calendar_type);
>
>/* The version of algorithm or data file used to compute dates
> * in the calendar. Will have the same format as the DNS records names,
> * e.g. 2001020304 for the fourth update in the day 3 February 2001.
> *
> * The update number is necessary for the islamic calendar, when news
> * of observance of new moons of Ramadan and Shawwal arrive from different
> * territories and the data file is updated (possibly hourly) from a server.
> */
>GCalendarVersion g_calendar_get_version   (const GCalendar *calendar);

What's wrong with using a guint32 here?


>/* The smallest date and latest the calendar supports (version-dependent).
>   Note that some values, while in the range, may still return UNKNOWN fields. */
>guint32          g_calendar_get_min_day   (const GCalendar *calendar);
>guint32          g_calendar_get_max_day   (const GCalendar *calendar);
>
>
>typedef guint8  GIntlDateDay;
>typedef guint8  GIntlDateMonth;
>typedef guint16 GIntlDateYear;
>typedef guint8  GIntlDateEra;
>
>typedef GDateWeekDay GIntlDateWeekday;
>#define G_INTL_DATE_BAD_WEEKDAY G_DATE_BAD_WEEKDAY
>#define G_INTL_DATE_MONDAY      G_DATE_MONDAY
>#define G_INTL_DATE_TUESDAY     G_DATE_TUESDAY
>#define G_INTL_DATE_WEDNESDAY   G_DATE_WEDNESDAY
>#define G_INTL_DATE_THURSDAY    G_DATE_THURSDAY
>#define G_INTL_DATE_FRIDAY      G_DATE_FRIDAY
>#define G_INTL_DATE_SATURDAY    G_DATE_SATURDAY
>#define G_INTL_DATE_SUNDAY      G_DATE_SUNDAY
>
>/* Era numbers can be (and usually are) zero. In some calendars (Hindu?), 0
>   is also a possible value for some fields. */
>#define G_INTL_DATE_BAD_JULIAN G_MAXUINT32
>#define G_INTL_DATE_BAD_DAY    G_MAXUINT8
>#define G_INTL_DATE_BAD_MONTH  G_MAXUINT8
>#define G_INTL_DATE_BAD_YEAR   G_MAXUINT16
>#define G_INTL_DATE_BAD_ERA    G_MAXUINT8

These are easier and less-error-prone written as ((GIntlDateDay) -1).


>#define G_INTL_DATE_UNKNOWN_JULIAN (G_INTL_DATE_BAD_JULIAN-1)
>#define G_INTL_DATE_UNKNOWN_DAY    (G_INTL_DATE_BAD_DAY-1)
>#define G_INTL_DATE_UNKNOWN_MONTH  (G_INTL_DATE_BAD_MONTH-1)
>#define G_INTL_DATE_UNKNOWN_YEAR   (G_INTL_DATE_BAD_YEAR-1)
>#define G_INTL_DATE_UNKNOWN_ERA    (G_INTL_DATE_BAD_ERA-1)
>
>typedef struct _GIntlDate GIntlDate;
>
>struct _GIntlDate
>{
>  const GCalendar *calendar;
>  guint32    julian_days;
>  
>  gboolean julian; /* julian is valid */
>  gboolean dmy;    /* dmye is valid */
>  
>  GIntlDateDay   day;
>  GIntlDateMonth month;
>  GIntlDateYear  year;
>  GIntlDateEra   era;
>  
>  gpointer       priv; /* in case some additional unpredicted information is needed to be kept for some calendars */
>};

If anything, the new struct should be opaque.


>GIntlDate*   g_intl_date_new              (const GCalendar *calendar);

>/* We call this 'dmy' instead of 'dmye' to make migration from GDate a little easier */

Unmeasurably easier, yeah...  Should be fixed :).

>GIntlDate*   g_intl_date_new_dmy          (const GCalendar *calendar,
>                                           GIntlDateDay     day,
>                                           GIntlDateMonth   month,
>                                           GIntlDateYear    year,
>                                           GIntlDateEra     era);
>
>GIntlDate*   g_intl_date_new_julian       (const GCalendar *calendar,
>                                           guint32          julian_day);
>void         g_intl_date_free             (GIntlDate       *date);
>
>gboolean     g_intl_date_valid            (const GIntlDate *date);
>gboolean     g_intl_date_valid_day        (const GCalendar *calendar,
>                                           GIntlDateDay     day);
>gboolean     g_intl_date_valid_month      (const GCalendar *calendar,
>                                           GIntlDateMonth   month);
>gboolean     g_intl_date_valid_year       (const GCalendar *calendar,
>                                           GIntlDateYear    year);
>gboolean     g_intl_date_valid_era        (const GCalendar *calendar,
>                                           GIntlDateEra     era);
>#define      g_intl_date_valid_weekday(cal, wd) \
>                                           g_date_valid_weekday (wd)
>gboolean     g_intl_date_valid_julian     (const GCalendar *calendar,
>                                           guint32          julian_date);
>gboolean     g_intl_date_valid_dmy        (const GCalendar *calendar,
>                                           GIntlDateDay     day,
>                                           GIntlDateMonth   month,
>                                           GIntlDateYear    year,
>                                           GIntlDateEra     era);
>
>GIntlDateWeekday g_intl_date_get_weekday           (const GIntlDate *date);
>GIntlDateDay     g_intl_date_get_day               (const GIntlDate *date);
>GIntlDateMonth   g_intl_date_get_month             (const GIntlDate *date);
>GIntlDateYear    g_intl_date_get_year              (const GIntlDate *date);
>GIntlDateEra     g_intl_date_get_era               (const GIntlDate *date);
>guint32          g_intl_date_get_julian            (const GIntlDate *date);
>guint            g_intl_date_get_day_of_year       (const GIntlDate *date);
>
>/* We may also have a function that allocates the GDate */
>void             g_intl_date_get_gdate             (const GIntlDate *date,
>                                                    GDate           *gdate);
>
>/* GDate's get_week_of_year functions are not supported with GIntlDate, as they don't necessarily
>   make sense for non-Gregorian calendars */

Well, a week is defined for GIntlDate.  A year is also defined.  A week of year can easily be defined also.

>void         g_intl_date_clear            (GIntlDate     *date,
>                                           guint          n_dates);

Not needed if opaque.

>void         g_intl_date_set_calendar     (GIntlDate       *date,
>                                           const GCalendar *calendar);
>/* Before calling these functions, g_intl_date_set_calendar must be called */
>void         g_intl_date_set_parse        (GIntlDate       *date,
>                                           const gchar     *str);
>void         g_intl_date_set_time_t       (GIntlDate       *date,
>                                           time_t           timet);
>void         g_intl_date_set_time_val     (GIntlDate       *date,
>                                           GTimeVal        *timeval);
>void         g_intl_date_set_gdate        (GIntlDate       *date,
>                                           const GDate     *gdate);
>void         g_intl_date_set_day          (GIntlDate       *date,
>                                           GIntlDateDay     day);
>void         g_intl_date_set_month        (GIntlDate       *date,
>                                           GIntlDateMonth   month);
>void         g_intl_date_set_year         (GIntlDate       *date,
>                                           GIntlDateYear    year);
>void         g_intl_date_set_era          (GIntlDate       *date,
>                                           GIntlDateEra     era);
>void         g_intl_date_set_dmy          (GIntlDate       *date,
>                                           GIntlDateDay     day,
>                                           GIntlDateMonth   month,
>                                           GIntlDateYear    year,
>                                           GIntlDateEra     era);
>void         g_intl_date_set_julian       (GIntlDate       *date,
>                                           guint32          julian_date);
>
>/* Perhaps not very useful, here just for compatibility with GDate */
>gboolean     g_intl_date_is_first_of_month     (const GIntlDate *date);
>gboolean     g_intl_date_is_last_of_month      (const GIntlDate *date);
>
>void         g_intl_date_add_days              (GIntlDate   *date,
>                                                guint        n_days);
>void         g_intl_date_subtract_days         (GIntlDate   *date,
>                                                guint        n_days);
>/* The day may change if the same day number does not exist in the new month */
>void         g_intl_date_add_months            (GIntlDate   *date,
>                                                guint        n_months);
>void         g_intl_date_subtract_months       (GIntlDate   *date,
>                                                guint        n_months);
>/* The day and/or month may change if the same day and/or month do not exist in the new year */
>void         g_intl_date_add_years             (GIntlDate   *date,
>                                                guint        n_years);
>void         g_intl_date_subtract_years        (GIntlDate   *date,
>                                                guint        n_years);


>gboolean     g_intl_date_is_leap_year          (const GCalendar *calendar,
>                                                GIntlDateYear    year);
>guint8       g_intl_date_get_days_in_month     (const GCalendar *calendar,
>                                                GIntlDateMonth   month,
>                                                GIntlDateYear    year);

These two should either:

  - Live under g_calendar_ namespace, or
  - Take GIntlDate and return leap-year and days-in-month of that date.

>gint         g_intl_date_days_between          (const GIntlDate *date1,
>                                                const GIntlDate *date2);
>
>/* qsort-friendly (with a cast...) */
>gint         g_intl_date_compare               (const GIntlDate *lhs,
>                                                const GIntlDate *rhs);
>
>void         g_intl_date_clamp                 (GIntlDate *date,
>                                        	const GIntlDate *min_date,
>                                        	const GIntlDate *max_date);
>void         g_intl_date_order                 (GIntlDate *date1, GIntlDate *date2);
>
>
>void         g_intl_date_to_struct_tm     (const GIntlDate *date,
>                                           struct tm   *tm);
>
>/* Like g_date_strftime but using CLDR format instead of strftime format. 
> * Some CLDR formatting codes will not be supported in the beginning, but
> * will be added if required.
> */
>gsize        g_intl_date_format           (gchar       *s,
>                                           gsize        slen,
>                                           const gchar *format,
>                                           const GIntlDate *date);
>
>#endif /* __G_INTL_DATE_H__ */

Ok, here's the main comment:  seems like the only reason GDate is duplicated is because the GDate struct is not opaque and hence can't be extended.

The whole GDate API looks prematurely optimized to me.  What I suggest however is to NOT duplicate GDate.  Do the following instead:

  - G_SEAL GDate and make it opaque for glib3.

  - Deprecate g_date_clear().

  - Redefine GDateMonth as an int.  For glib3, redefine all of GDateDay/Month/Year, etc as guint, instead of guint8, guint16, etc as there's no benefit in defining them so.

  - Add g_date_set_calendar().

Implementation details: g_date_clear() and g_date_set_calendar() can't work together.  set_calendar() on _clear()ed dates fails and uses the old struct definitions.

Something like that will have my full support to go in...
Comment 13 Dan Winship 2009-05-13 15:00:20 UTC
a few miscellaneous thoughts:

Assuming that the eventual goal is to use non-Gregorian calendars in evolution and/or the panel clock, the API seems like it might be insufficient (in that it doesn't give GtkCalendar quite enough information to know how to draw the various calendar systems). Probably the GtkCalendar changes/replacement should be developed in parallel with the GCalendar/GIntlDate/whatever API.

Maybe GIntlDate should have an "uncertain" flag? Eg, as I understand it, you won't know exactly when a given future date in the (observational) Islamic calendar is going to fall until the start of the month has been observed, so conversions to/from other calendars may end up being off by a day or two.

(Another sort of uncertainty happens with calendars with eras; the year 2100 won't really be "Heisei 112" in the Japanese calendar, but we don't know what else to call it right now...)

> > /* GDate's get_week_of_year functions are not supported with GIntlDate, as they don't necessarily
> >    make sense for non-Gregorian calendars */
> 
> Well, a week is defined for GIntlDate.  A year is also defined.  A week of year
> can easily be defined also.

Yeah, but there's no reason to. Week numbers are just this arbitrary convention used mostly by businesses in Europe. It's like the zodiac animals in the Chinese calendar. You wouldn't say "It's the Year of the Ox in the Chinese calendar, but it's the Year of the Rat in the Gregorian calendar" because that makes no sense; what animal year it is is defined by the Chinese calendar, and likewise what ISO week number it is is defined by the Gregorian calendar.

> gboolean     g_intl_date_is_leap_year          (const GCalendar *calendar,
>                                                 GIntlDateYear    year);

"Leap year" is too specific a concept. Some calendars have several different lengths of years, or different places in the calendar where the "leap" days/months might be inserted in different years. This ties in to what I was saying about implementing GtkCalendar; GCalendar/GIntlDate will need to have APIs that work in ways so that callers wouldn't need to explicitly think about these things. (You'd just ask for a list of months, and how many days in each, and use things like g_intl_date_days_between() etc for math.)

> Ok, here's the main comment:  seems like the only reason GDate is duplicated is
> because the GDate struct is not opaque and hence can't be extended...

If we're going to reinvent GDate it would be nice to think about having GDateTime with better timezone support too... I thought there was a bug about that, but searching for glib bugs with "zone" in the summary doesn't turn anything up...
Comment 14 Behdad Esfahbod 2009-05-21 08:20:47 UTC
(In reply to comment #13)
> a few miscellaneous thoughts:
> 
> Assuming that the eventual goal is to use non-Gregorian calendars in evolution
> and/or the panel clock, the API seems like it might be insufficient (in that it
> doesn't give GtkCalendar quite enough information to know how to draw the
> various calendar systems). Probably the GtkCalendar changes/replacement should
> be developed in parallel with the GCalendar/GIntlDate/whatever API.

Good point.  I'll look into GtkCalendar.


> Maybe GIntlDate should have an "uncertain" flag? Eg, as I understand it, you
> won't know exactly when a given future date in the (observational) Islamic
> calendar is going to fall until the start of the month has been observed, so
> conversions to/from other calendars may end up being off by a day or two.

One always will have a guesstimate I guess.  Not sure if that helps.
 
> (Another sort of uncertainty happens with calendars with eras; the year 2100
> won't really be "Heisei 112" in the Japanese calendar, but we don't know what
> else to call it right now...)

The past is not uncertain.  It's incontinuous at times.  That's different.  A PITA to implement though, I agree.


> > > /* GDate's get_week_of_year functions are not supported with GIntlDate, as they don't necessarily
> > >    make sense for non-Gregorian calendars */
> > 
> > Well, a week is defined for GIntlDate.  A year is also defined.  A week of year
> > can easily be defined also.
> 
> Yeah, but there's no reason to. Week numbers are just this arbitrary convention
> used mostly by businesses in Europe. It's like the zodiac animals in the
> Chinese calendar. You wouldn't say "It's the Year of the Ox in the Chinese
> calendar, but it's the Year of the Rat in the Gregorian calendar" because that
> makes no sense; what animal year it is is defined by the Chinese calendar, and
> likewise what ISO week number it is is defined by the Gregorian calendar.

No strong opinion.


> > gboolean     g_intl_date_is_leap_year          (const GCalendar *calendar,
> >                                                 GIntlDateYear    year);
> 
> "Leap year" is too specific a concept. Some calendars have several different
> lengths of years, or different places in the calendar where the "leap"
> days/months might be inserted in different years.

Do they?  I agree that it's an unnecessary abstraction.


> This ties in to what I was
> saying about implementing GtkCalendar; GCalendar/GIntlDate will need to have
> APIs that work in ways so that callers wouldn't need to explicitly think about
> these things. (You'd just ask for a list of months, and how many days in each,
> and use things like g_intl_date_days_between() etc for math.)

Indeed.  That's how I implemented my calendar (http://behdad.org/calendar)

> > Ok, here's the main comment:  seems like the only reason GDate is duplicated is
> > because the GDate struct is not opaque and hence can't be extended...
> 
> If we're going to reinvent GDate it would be nice to think about having
> GDateTime with better timezone support too... I thought there was a bug about
> that, but searching for glib bugs with "zone" in the summary doesn't turn
> anything up...

I really like reviving GDate for this right now.  Timezone is a totally different beast that can build on top of a date API I hope...
Comment 15 Christian Hergert 2009-10-04 23:36:12 UTC
I started putting together a GDateTime branch which is located at http://git.dronelabs.com/glib.

Couple things in general with where I wanted to go with this.

GDateTime itself should probably just match the ISO standard.  It can be used alone without much regard to calendars in many cases and is simply an offset from Midnight on 1/1/1.  I think this is especially true when you are building components that are not related to a particular UI (timestamps, etc) so its an acceptable trade-off.  It makes preventing loss of precision much easier to implement when migrating between various calendars (at least from what i can tell).

Creating a GCalendar which can do conversions to specific calendars would allow for maximum flexibility for each calendar while not increasing the complexity of the common use-case.  I think this is what Dan was getting at.  For example, to get the year in the Persian calendar:

 GCalendar *c = g_persian_calendar_new ();
 gint year = g_calendar_get_year (c, datetime);

I imagine a helper for current locale would be nice too.

 GCalendar *c = g_calendar_current ();

What I like about this approach is it lets us get GDateTime moving forward in the meantime while we get the various experts on the different calendar systems to weigh in.

I'd also like to have GTimeZone with information on the timezone for the GDateTime instance.  However, I have not yet had any luck on getting tzone information for anything other than localtime's timezone.  I guess we could lift the tzone parser from libc (its LGPL) and parse the tzone database ourself.  Again, I think GDateTime can move forward until we come up with a valid API for GTimeZone.

So to recap, what I'd like to see is

1) GDateTime cleanup/polish/stablizing in near future for inclusion
2) Rough plan of GTimeZone API while we determine how to best get that data
3) Rough plan of GCalendar and what is common across all calendars (while the specifics can land in each calendar implementation).

Of course, I'm sure many of you know this stuff better than me, so feel free to tell me to piss up a rope.
Comment 16 John Ralls 2009-10-05 03:19:17 UTC
I suggest sacrificing a digit of precision (usec should be good enough for most usage) to allow covering 9999BCE to 9999CE. That will include the epochal origins (i.e., year 1) of most calendars and reduce the likelihood of conversion issues. 

Ideally, I think, the private date would be stored as the Julian Period Number used by astronomers or some similar calendar-independent value. The default accessors could automagically re-present it as a Gregorian date-time consistent with ISO 8601... or even as a time_t for legacy/POSIX use.
Comment 17 Christian Hergert 2009-10-05 03:45:21 UTC
(In reply to comment #16)
> I suggest sacrificing a digit of precision (usec should be good enough for most
> usage) to allow covering 9999BCE to 9999CE. That will include the epochal
> origins (i.e., year 1) of most calendars and reduce the likelihood of
> conversion issues. 

What justifies usec as "good enough?"  I'd like to provide the highest possible resolution within a reasonable period of time.  (I think 1 to 9999 is reasonable).  I'm not convinced that historical dates before year 1 is a requirement for a *general purpose* date-time.
Comment 18 John Ralls 2009-10-05 04:34:37 UTC
With time as with most measurements, the larger the magnitude of the measurement, the less resolution is useful. High resolution measurements are only useful to compare to other high resolution measurements with a small enough difference that the resolution matters. Needing even a 1-second resolution is rare indeed for calendrical calculations. It might be appropriate to have a nanosecond-resolution timer for some physics experiment or to test the response of an integrated circuit (not much difference there, I know), but it isn't terribly important to know, even to the second, when President Kennedy began his inauguration speech, or when he was shot in Dallas. No one will notice if I'm 2 seconds early or 2 seconds late to my meeting tomorrow. 

Add to that that the ability to measure factions of a second is a recent invention, such that a nanosecond time before 1960 or so is spurious; even an hour time before 1500 is similarly spurious.

So what problem are you trying to solve? Why provide a computational era of 10,000 years when only 2,000 have historical value? Do you have some conceit that your scheme will be in use even 100 years from now, never mind 1000?

ISTM that it would make conversions easier if year 1 of all calendars could be accommodated within the era of the scheme. Since the Islamic calendar is the only one in common use with an origin after the beginning of the Common Era, using the origin of the Common Era as the earliest date that your system can accommodate doesn't support that. On the other hand, if historical dates are of no interest, why provide such a large range of dates? Why go to the trouble of writing a new date/time scheme? After all, the current one works fine, is highly portable, and with 64-bit integers won't expire in 28 years.
Comment 19 Christian Hergert 2009-10-29 06:06:44 UTC
Created attachment 146480 [details]
gdatetime/gcalendar idea

So I took another stab at a portion of this based on some of the comments
from John.  Included in this is:

 * Year/Month/Date is determined using the Julian Period and Day Number within
   said period.
 * Time-keeping is reduced to microseconds and stored as a separate field.

In general, my thought was that GDateTime could have Gregorian based accessors
and GCalendar implementations could be used for localizing the GDateTime.

Other notable information

 * GDateTime is an opaque type stored on the heap.
 * GDateTime is an immutable type. Adding days/months/years/minutes or whatever
   results in a new GDateTime instance which was requested.
 * GDateTime's accessors are Gregorian, even though internal format is Julian.
   g_date_time_get_julian() provides calendars a way to get that information
   directly.
 * GCalendar is a GObject.  I was thinking the benefit of object inheritance
   would be useful enough where we could keep just gregorian in libglib and
   copious calendars in libgobject.  I think this would make it easier to add
   calendars over time.

Things I feel are missing

 * GCalendar doesn't yet have its own "printf" method.  This is needed
   so that calendars can be used to provide localized month names rather than
   just the gettext translation of gregorian month names.
 * Like John mentioned, the further you get from our current time, the less
   important time-keeping is.  Therefore, it might be neat to reclaim these
   37-bits for a larger date range when the Julian Period is not 0 (current).
   Currently dates such as C.E. +- 20,000 years work.
 * Parsing is partly there.  Needs more work but serves as an example to
   solicit feedback.

Anyway, thanks for reading this far and feedback is welcomed.
Comment 20 Carolyn_MacLeod 2010-08-12 18:51:01 UTC
This bug, and the GtkCalendar bugs that depend on this bug are now blocking Eclipse bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=273205.
Comment 21 Matthias Clasen 2010-08-26 03:50:45 UTC
GDateTime has been merged now
Comment 22 Christian Persch 2010-10-15 10:39:53 UTC
*** Bug 632194 has been marked as a duplicate of this bug. ***
Comment 23 Emmanuele Bassi (:ebassi) 2013-10-24 10:19:39 UTC
*** Bug 710768 has been marked as a duplicate of this bug. ***
Comment 24 Emmanuele Bassi (:ebassi) 2013-10-24 10:23:12 UTC
as I said in bug 710768, GDateTime is already defined in terms of the Proleptic Gregorian Calendar:

   http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar

the correct place to have API to transform GDateTimes between calendars is a separate API, like the one Christian was outlining in comment 15. the system calendar can be determined by the locale used.
Comment 26 Amir Mohammadi 2015-07-28 04:51:37 UTC
Hello everyone,

I have been trying to fix this bug for a while now and I wanted to share some thoughts with you. This is a very old bug and a lot has changed since then. For example GDateTime has been merged in glib since then.

If anyone is trying to get more general info about this bug, I highly recommend reading the Calendar FAQ:
http://www.tondering.dk/claus/calendar.html

This issue has been fixed in KDE years ago and the documentation is available here:
https://community.kde.org/KDE_Core/KCalendarSystem
I think we can copy a lot of things from them since their license is also compatible with glib.
Also, John Layt (a KDE contributor) has some nice slides about "Defining Common Standards for Calendar Systems and Holidays" which are useful to read:
https://desktopsummit.org/program/sessions/defining-common-standards-calendar-systems-and-holidays

Christian Hergert put an initial code for this bug which was very helpful and I found a copy of it here:
https://github.com/chergert/gdatetime
Please let me know if there are more recent versions available.
I think one part of the problem that is not present in these codes are a functionality like "g_date_time_format". Although the current implementation is good for keeping different calendars internally, there is no way of representing these calendars to the user. So, my question is like how would you think this part should be implemented? Especially, how can we use the code that is already available in "g_date_time_format"'s implementation?
Comment 27 Christian Hergert 2015-07-28 21:57:57 UTC
The code in my github repository is before anything landed in GLib. It went through a bunch of revisions in the process. Including the implementation of GTimeZone and the removal of GCalendar.

I do wonder what a minimal API would look like to add support for this. For example, would we expect the developer to get a GCalendar and format the date time using it instead? Something like:

  g_calendar_format (GCalendar *, const gchar *format, GDateTime *)

That way, we keep the GDateTime as is and calendars are only used for translating to end user strings?

There must be more things we want, right? Would we expect it to be possible to create a visual calendar by accessing information from the GCalendar? (For example, should GNOME Calendar be able to render calendars other than Gregorian?)
Comment 28 John Ralls 2015-07-28 23:32:26 UTC
I do think you want to leave GDateTime as-is to avoid needless code breakage, but a GCalendar class should certainly do more than display strings, like calculating past or future GDateTimes given a span in days, weeks, months, and years.

Once that's done it's pretty easy to work up a simple API for retrieving what you need to display a calendar, so sure, why not generalize GtkCalendar to work with a GCalendar?
Comment 29 Amir Mohammadi 2015-07-29 07:50:08 UTC
Eventually I (or we) want every date represented in the system in another Calendar system. For example, when I go to the setting -> "Region and Language" and change the format to Iran, I want every date in the system be represented in Jalali Calendar system like: CheharShanbe, 7 Mordad 1394 or (چهارشنبه‌‌, ۷ مرداد ۱۳۹۴) instead of Wednesday, 29 July 2015. These dates are everywhere in Nautilus, Gnome Shell, Gnome Calendar, and etc. Eventually all of these programs should "easily" port their programs from using g_date_time to g_calendar.

A simple usage would be something like this (for example in Nautilus):

afile = "/myfile.txt";
GDateTime * modified_time = magic_get_last_modification_date(afile);
GCalendar * current_calendar_system = g_calendar_from_locale();
printf("The file was last modified at: %s", g_calendar_format(current_calendar_system, modified_time, "%c");
# %c: the preferred date and time rpresentation for the current locale

Or for example in Gnome Calendar:

GDateTime * today = g_date_time_new_now_local();
GCalendar * jalali_calendar = g_calendar_jalali_new();
# user wants to move the calendar to one month later.
# adding days, months, years, ... should be done in the relevant calendar system
# because adding 1 month in Gregorian calendar system does not necessarily
# mean that you are adding 1 month in Jalali (some call it Persian too) 
# calendar too.
GDateTime * one_month_later = g_calendar_add_month(jalali_calendar, today, 1);

I think we need to provide functions similar to:
http://api.kde.org/4.x-api/kdelibs-apidocs/kdecore/html/classKCalendarSystem.html
And I was going at it too and created these functions:
GType       g_calendar_get_type         (void) G_GNUC_CONST;
GCalendar * g_calendar_from_locale      (void);
GDateTime * g_calendar_get_earliest_valid_date (GCalendar *calendar);
GDateTime * g_calendar_get_latest_valid_date   (GCalendar *calendar);
GDateTime * g_calendar_get_epoch               (GCalendar *calendar);
gint        g_calendar_get_year                (GCalendar *calendar, GDateTime *datetime);
gint        g_calendar_get_month               (GCalendar *calendar, GDateTime *datetime);
gint        g_calendar_get_day_of_month        (GCalendar *calendar, GDateTime *datetime);
gint        g_calendar_get_day_of_week         (GCalendar *calendar, GDateTime *datetime);
gint        g_calendar_get_day_of_year         (GCalendar *calendar, GDateTime *datetime);
gint        g_calendar_get_hour                (GCalendar *calendar, GDateTime *datetime);
gint        g_calendar_get_minute              (GCalendar *calendar, GDateTime *datetime);
gint        g_calendar_get_second              (GCalendar *calendar, GDateTime *datetime);
gboolean    g_calendar_is_leap_year            (GCalendar *calendar, GDateTime *datetime);
gboolean    g_calendar_is_lunar                (GCalendar *calendar);
gboolean    g_calendar_is_lunisolar            (GCalendar *calendar);
gboolean    g_calendar_is_proleptic            (GCalendar *calendar);
gboolean    g_calendar_is_solar                (GCalendar *calendar);
gboolean    g_calendar_is_valid                (GCalendar *calendar, GDateTime *datetime);
gchar *     g_calendar_format                  (GCalendar *calendar, GDateTime *datetime, const gchar *format);
gint64      g_calendar_get_julian_day   (GDateTime *datetime);

This is when I realized implementing "g_calendar_format" would be very hard (for me at least) if we were to implement it in each calendar system. I think (I might be wrong, I am not very good at this) we need a function like "g_date_time_format_locale" implemented in GCalendar too but I was also thinking how can we reuse this "g_date_time_format_locale" function.
Comment 30 GNOME Infrastructure Team 2018-05-24 10:49:22 UTC
-- GitLab Migration Automatic Message --

This bug has been migrated to GNOME's GitLab instance and has been closed from further activity.

You can subscribe and participate further through the new bug through this link to our GitLab instance: https://gitlab.gnome.org/GNOME/glib/issues/55.