GNOME Bugzilla – Bug 344005
Add a class for supporting multiple, non-Gregorian calendar formats
Last modified: 2018-05-24 10:49:22 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.
Created attachment 66822 [details] suggested prototype gcalendar.h
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
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
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.
We had a similar request in https://launchpad.net/distros/ubuntu/+source/glib2.0/+bug/62589
*** Bug 391626 has been marked as a duplicate of this bug. ***
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
(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.
I don't think we want to make glib depend on another library anyway, so it's kinda moot.
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?
(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 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...
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...
(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...
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.
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.
(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.
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.
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.
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.
GDateTime has been merged now
*** Bug 632194 has been marked as a duplicate of this bug. ***
*** Bug 710768 has been marked as a duplicate of this bug. ***
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.
possible prior art for a GCalendar API: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSCalendar_Class/Reference/NSCalendar.html#//apple_ref/occ/cl/NSCalendar
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?
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?)
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?
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.
-- 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.