Closed araselahi closed 3 years ago
There's no datepicker in material2. $mdDateLocale
sounds like material. You are probably in a wrong repo.
I just saw datepicker on material2 feature status: https://github.com/angular/material2#feature-status thought that it would inherit the same functionality from material. Hope you take internationalization date conversion into account for new datepicker. We all love material(2) and your great project.
The problem with datepicker, as mentioned in its issue, is that there are no specs(for desktop version anyway) in material design for it.. so until google designers create those, there won't be no official date/timepicker implementation. Sadly.
But I'm pretty sure that once the spec is done, internationalization will be taken into account.
Internationalization date conversion may not affect visual design of datepicker. It just refer to how cells arrange according to the native calendar rules, mainly the same functions as currently exist in dateUtil, but with native alternative principals. About the visual customization, task already has been done gracefully in $mdDateLocale.
We're designing the datepicker now. We likely will only be able to support the Gregorian calendar in the initial version, but it's worth thinking about in the design.
At the moment it may not important to support any special native calendar. The important point about this feature is to make the logic of date conversion 'injectable'. Then every folk will know how to implement their native conversion logic providers. Although I had some suggestions above, but as a designer, you know better how to construct the structure. Thanks Jeremy, for taking it into account and for your grate project.
I'll have a design doc to share shortly, it would be great if you could take a look and let me know if you think the design is flexible enough to support this.
of course, it would be my pleasure.
@araselahi design doc is now available: https://docs.google.com/document/d/1RwPFESCbSSLMSDqxWo7jg4UUpGJTrXX6p5wYlb9pmNc/preview
I have just seen the notification in my email. I will get back to you soon.
@mmalerba 'CalendarLocale injectable' and 'Internationalization' sections currently include the definition only about the word translation and visual parsing and formatting but not other nationality date conversion logic. In other calendars, days calculation completely different from Gregorian. So I guess we need all the logic in dateUtil.js such as getFirstDateOfMonth, getNumberOfDaysInMonth and so on could be replaceable according to the other international calendar to determine how the days to be rendered on date picker. I think with this suggested feature, it would be a magic date picker for other folks. I am not sure the new SimpleDate data type could support other international calendar date because it is still subset of Gregorian calendar date. Am I right?
@araselahi I'd like to find some way to allow using moment.js objects instead of SimpleDate
when the library is loaded. moment.js already has plugins for working with other languages. I still need to find the best way to do this though
As @araselahi mentioned, it is not a matter of translation from English to another language. In Persian calendar for example the whole logic of the date system is different. 2017-April-15 may be translated into 2017 آپریل 15 which has nothing to do with Persian calendar for our daily usage which is 1396-1-26 yyyy-MM-dd. It is 26th day of the first month of the year 1396, but in Gregorian calendar it is 15th of the forth month of the year 2017.
@peymanebrahimi it completely depends on how to implement Moment.js. If the output of configured Moment.js object was rendered on the final date picker surface as @mmalerba designed for, that would be great. Please check it out: https://momentjs.com/docs/#/plugins/jalaali/
@mmalerba does what we have now actually support different calendars, or are there assumptions about the Gregorian calendar baked in?
I think we still have some assumptions baked in, but it should be easy enough to change them to settings in the DateAdapter
going to mark this one as good for community contribution. It would be nice to have someone who knows more about other calendars help with it.
@mmalerba with DateAdapter
implementation, currently you have done the critical task for calendar logic internationalization. thank you so much for your consideration. I am here to contribute for implementing JalaliDateAdapter
. is there any sample like: How to implement a new DateAdapter
?
@peymanebrahimi has been working on a Jalali adapter https://github.com/angular/material2/issues/5972#issuecomment-329956087, you may want to consider collaborating with him.
Currently the best documentation is just the JSDoc on the DateAdapter
https://github.com/angular/material2/blob/master/src/lib/core/datetime/date-adapter.ts
You can also use the NativeDateAdapter
and MomentDateAdapter
as example implementations:
https://github.com/angular/material2/blob/master/src/lib/core/datetime/native-date-adapter.ts
https://github.com/angular/material2/blob/master/src/material-moment-adapter/adapter/moment-date-adapter.ts
Sorry it doesn't have better documentation yet... I need to work on that. @araselahi
Have you thought of supporting Julian Day Numbers for internal conversions from one calendar format to another? There are already JS libraries around that could possibly be used for the conversions (e.g., http://npmjs.com/package/moonbeams).
I am interested in developing the following features:
@tobiasschweizer choosing a different precision is tracked by https://github.com/angular/material2/issues/4853, this is something we plan to add
BCE dates and a Julian calendar are not features we plan to add to Angular Material, but they are features that we want enable via a custom DateAdapter
. If you're interested in working on these we would love for you to share it with the community. If you encounter any limitations of the DateAdapter
that prevent you from accomplishing this, feel free to open issues and we will look into it.
@mmalerba Thank you very much for the explanation!
Let me have a look at DateApapter
first. I guess I find some relevant information here https://material.angular.io/components/datepicker/overview#choosing-a-date-implementation-and-date-format-settings
As far as I understood, DateAdpater
is an interface/abstract class (https://github.com/angular/material2/blob/dfe8091d959f1bb54cc33a8d942cbe7ea4e3fe56/src/lib/core/datetime/date-adapter.ts#L21) making use of generics that currently has two implementations: Native Date Adapter (https://github.com/angular/material2/blob/ea54edb7ef1c5338120c3991acbe325ad7c27764/src/lib/core/datetime/native-date-adapter.ts#L60) and the Moment Date Adapter (https://github.com/angular/material2/blob/9fa075e001188f1a346dbc260606e871bc653e1a/src/material-moment-adapter/adapter/moment-date-adapter.ts#L34).
@tobiasschweizer Yep, that's correct. I've been meaning to write a guide on creating a custom DateAdapter
but haven't gotten the time. Those two concrete classes should serve as good examples though.
@mmalerba Thats' fine. Let me look at it in detail. I will come up with some suggestions of how to integrate the requested features, so we can discuss them.
@mmalerba This is a primitive first sketch of how I think this could work (I do not address the BCE problem yet, divide et impera ...). I hope I got the basic idea of the design and this contribution is useful.
The DateAdapter
would have two new abstract methods getSupportedCalendars(): string[]
and convertCalendar(cal: D, toFormat: string): D
. The DatePicker
UI component could display a list of available calendars to the user. If the user chooses another calendar than the one currently given, a conversion would happen from the current calendar to the chosen one.
JDNCalendar
is an abstract class that would have to be implemented/subclassed for all supported calendar types (here only Gregorian and Julian). The clue would be that every implementation or subclass of JDNCalendar
can be created from a Julian Day Number and convert itself to a Julian Day Number. Like this, a Gregorian calendar date can be converted to a Julian calendar date etc., preserving the currently selected date.
We have implemented that earlier, but the code base is a mess: https://github.com/dhlab-basel/Knora/blob/develop/salsah/src/public/js/datehelpers.js, https://github.com/dhlab-basel/Knora/blob/develop/salsah/src/public/js/jquery.dateobj.js
However, the new abstract methods for DateAdapter
do not make sense for the existing implementations since there is no conversion between calendars that could happen. In this case, the DatePicker
should not show them (if getSupportedCalendars()
returns an array of length 1).
const Gregorian = 'Gregorian';
const Julian = 'Julian';
/**
* Represents a calendar conversion error.
*/
class CalendarConversionError extends Error {
constructor(readonly message: string) {
super(message)
}
}
/**
* Abstract class representing a calendar format
* that can be converted from and to a Julian Day Number (JDN).
*/
abstract class JDNCalendar {
// type of calendar
calendarType: string;
// supported calendars
readonly supportedCalendars = [Gregorian, Julian];
/**
* Convert an instance of JDNCalendar to a JDN.
*
* @returns {number}
*/
abstract toJDN(): number;
/**
* Converts from one calendar format into another.
*
* @param {"Gregorian" | "Julian"} toCalendarType
* @returns {JDNCalendar}
*/
convertCalendar(toCalendarType: string): JDNCalendar {
if (this.supportedCalendars.indexOf(toCalendarType) == -1) throw new CalendarConversionError("Calendar not supported: " + toCalendarType);
if (this.calendarType == toCalendarType) return this; // no conversion needed
const jdn = this.toJDN();
switch (toCalendarType) {
case Gregorian:
return new GregorianCalendar(jdn);
case Julian:
return new JulianCalendar(jdn);
}
}
}
/**
* Represents a Gregorian calendar date.
*/
class GregorianCalendar extends JDNCalendar {
/**
* @param {number} jdn Julian Day Number
*/
constructor(jdn: number) {
super();
// create a Gregorian calendar date from jdn
}
readonly calendarType = Gregorian;
toJDN(): number {
return 1; // for testing only
}
}
/**
* Represents a Julian calendar date.
*/
class JulianCalendar extends JDNCalendar {
/**
*
* @param {number} jdn Julian Day Number
*/
constructor(jdn: number) {
super();
// create a Julian calendar date from jdn
}
readonly calendarType = Julian;
toJDN(): number {
return 2; // for testing only
}
}
/*
new abstract methods for DateAdapter:
- getSupportedCalendars(): string[]
- convertCalendar(fromCal: D, toFormat: string)
*/
/**
* DateAdapter for JDNCalendar
*/
class JDNCalendarAdapter extends DateAdapter<JDNCalendar> {
// TODO: implement all abstract methods of DateAdapter and override non abstract methods of DateAdapter if necessary
getSupportedCalendars(cal: JDNCalendar): string[] {
return cal.supportedCalendars;
}
convertCalendar(cal: JDNCalendar, toFormat: string): JDNCalendar {
return cal.convertCalendar(toFormat);
}
}
I don't think we want to add calendar conversion APIs to the DateAdapter
(since as you said, it doesn't make sense in a lot of cases). Rather I would make a JulianCalendarDateAdapter
that handles things like parsing and formatting Julian dates, and getting the julian year, month, and day.
If you then wanted to be able to dynamically toggle between the two calendars you could make a wrapper DateAdapter
that delegates to one of the two depending on the mode its in:
class CombinedAdapter extends DateAdapter {
private gregorianAdapter = new NativeDateAdapter();
private julianAdapter = new JulianCalendarDateAdapter();
private currentAdapter = this._gregorianAdapter;
setCalendar(cal: 'Gregorian' | 'Julian') {
this.currentAdapter = cal === 'Gregorian' ? this.gregorianAdapter : this.julianAdapter;
}
// Implement all methods to delegate to currentAdapter:
parse(value) {
return this.currentAdapter.parse(value);
}
...
}
The one thing we might need to change to make this work is to add some way to flag the datepicker that the DateAdapter
has changed modes and that the datepicker should update its view.
I understand that the calendar conversion method in DateAdapter
does not make sense for implementations where only one calendar is available.
However, the JDNCalendar
I proposed is not the same as the Julian calendar date.
A JDN (Julian Day Number) is an integer (no fractions unlike the Julian Day Count JDC) that represents the number of days since an astronomical event (JDN 0) of November 24, 4714 BC, in the Gregorian calendar or Monday, January 1, 4713 BC, Julian calendar (https://en.wikipedia.org/wiki/Julian_day).
Both Gregorian and Julian calendar dates (as well as others) can be created from a JDN and converted to a JDN. Using JDN, one can thus do calendar conversions (see https://www.fourmilab.ch/documents/calendar/).
So my idea is to create an implementation of DateAdapter
for JDNCalendar
(maybe I have to think of a better name for the latter) that could convert any calendar format to any other calendar format for which an implementation for JDNCalendar
exists, preserving the currently selected date.
Subclasses or implementations of JDNCalendar
can be constructed from a given JDN and converted to a JDN, from which others can be constructed again. So the JDN is like the common denominator for different calendar formats.
If I understood you correctly, you proposed a specific implementation of DateAdapter
for Julian calendar dates. This would mean that we would have to make a specific implementation for each calendar we want to support. Instead, I propose to make use of one abstract class (JDNCalendar
) for which only one implementation of DateAdapter
has to exists and which can also be implemented for other than Gregorian and Julian calendar formats if they can be converted from and to JDN , e.g., Hebrew calendar.
Ah I see, I had not heard about Julian Day Number before. In that case I think your proposal sounds reasonable. Would the JDNCalendar
then be responsible for reporting things like what the months are in its particular calendar system?
Another thing to consider is the parse and display formats. We allow the user to provide mappings for the formats needed by the datepicker via the MAT_DATE_FORMATS
token. It sounds like for your idea you would need to come up with a way to represent the format independent of the calendar system. It's kind of the same problem I was trying to solve when I made these mappings in the first place, you could just map each key to a string of the same name and have each concrete JDNCalendar
decide how to represent it itself. If we added more formats to the datepicker at some point you would need to just add those to your implementations as well.
I really like your design that separates the UI-component (datepicker) from a concrete implementation of a calendar (currently JS Date object and MomentJS) using DateAdapter
as the interface through which communication between the UI and the calendar is established.
This abstraction greatly enhances the re-usability of the UI component. However, I also see things that are actually calendar specific like the distinction between BCE and CE dates and only make sense for the Gregorian and the Julian calendar.
Would the JDNCalendar then be responsible for reporting things like what the months are in its particular calendar system?
Yes, JDNCalendar
would have to have methods that tell the UI-component everything it needs to know to make the date selection possible for the given calendar (e.g., the Hebrew calendar's months are called differently than the months of the Gregorian and Julian calendar). But this would mean that DateApater
would have to be extended for that purpose so that the UI component can ask the implementation of DateApter
about it. But there are already methods for that in DateAdapter
:
getMonthNames()
getDayOfWeekNames()
Although we aim to be as generic as possible, the UI component might be limited to calendars that work with a concept of years, months, and days.
Another thing to consider is the parse and display formats.
I think localization and format information has to be provided for each implementation of JDNCalendar
.
How shall we proceed? I would like to work on JDNCalendar
and support implementations for the Gregorian and Julian calendar for now. Of course, implementations for other calendars may be added too.
I think JDNCalendar
can be developed outside Angular Material 2 repo, but we need to decide on the additional methods for DateAdapter
, and that will affect existing implementations for Date object and Moment JS.
See my sketch:
Another thing to keep in mind is the precision. A precision can by expressed using two JDNs (see http://www.knora.org/documentation/manual/rst/knora-ontologies/knora-base.html#datevalue, https://github.com/dhlab-basel/Knora/blob/cc53ef8cd072b7c9c53c7e5b3ab3a92c497b3038/webapi/src/main/scala/org/knora/webapi/util/DateUtilV1.scala). If the two numbers are equal the precision is day. Month precison would be from the first day of the month to the last and the same logic applies to the year. Periods may be expressed by simply having two datepickers, i.e. two dates that might have different precisions.
I am having a look at the abstract methods of DateAdapter
and the ones that are overridden by its subclasses.
I am quite sure I forgot some of the methods or misunderstood some of them, please feel free to correct me ;-)
date: D
as parameter (get information from or modify a date instance):getYear(date: D): number
: returns the given date's year as a number (e.g., 2017).getMonth(date: D): number
: returns the given date's month, starting with index 0 for January.getDate(date: D): number
: returns the index for the day of the month for the given date, starting with 1. Wouldn't it be clearer to use the term "day"?getDayOfWeek(date: D): number
returns the index for the day of the week for the given date, starting with 0 for SundaygetYearName(date: D): string
: returns the given date's year as a string Isn't that somehow redundant? -> getYear(date: D): number getNumDaysInMonth(date: D): number
: returns the number of days for the given date's month.addCalendarYears(date: D, years: number): D
: increases the given date by the given amount of yearsaddCalendarMonths(date: D, months: number): D
increases the given date by the given amount of monthsaddCalendarDays(date: D, days: number): D
: increases the given date by the given amount of daysisValid(date: D): boolean
: returns true if the given date is validisDateInstance(obj: any)
: returns true if obj is of type D (the type of date supported by the implementation of DateAdapter
)getMonthNames(style: 'long' | 'short' | 'narrow'): string[]
: returns the names of the months as an ordered listgetDateNames(): string[]
: returns an ordered list of strings "1" to "31" (regardless of how many days the current date's month has) Wouldn't it be clearer to use the term "day"?getDayOfWeekNames(style: 'long' | 'short'): string[]
: returns an ordered list of the names of all days of the week, starting with SundaygetFirstDayOfWeek(): number
: returns the index of the first day of the week, i.e. 0 (Sunday)clone(date: D)
: returns a copy of the given date.createDate(year: number, month: number, date: number): D
constructs a date Wouldn't it be clearer to use the term "day" for the last parameter? today(): D
: returns a date representing today's dateparse(value: any, parseFormat: any): D | null
: constructs a date from a user provided value, taking into account locales if possible (e.g., for Moment JS)deserialize(value: any): D | null
: attempts to construct a date from the provided valueinvalid()
: constructs an invalid date instanceformat(date: D, displayFormat: any): string
: serializes the given date to a string, using the given format toIso8601(date: D): string
: serializes the given date to a ISO date stringI think each implementation or subclass of JDNCalendar
has to provide an own implementation of the methods in group 2 since these are specific to the calendar format.
For createDate
, parse
and deserialize
as well as today
in group 3 an indication of the calendar format is needed. Gregorian could be made the default, so existing implementations wouldn't break.
When serializing the date, the calendar information has to go somewhere (group 4), it could be omitted for Gregorian.
For the year (group 1), BCE dates could be tricky.
Apologies for the slow response. I agree with what you've outlined above. I think this is a cool idea, it would be great for people who need a more robust solution for dealing with multiple calendars.
Here's some thoughts & rationale for some of the decisions:
But there are already methods for that in DateAdapter:
getMonthNames()
getDayOfWeekNames()
Although we aim to be as generic as possible, the UI component might be limited to calendars that work with a concept of years, months, and days.
Yes this does limit us to calendars that use years, months, and days. Though I think that should cover the vast majority of calendars people would want to use with the datepicker. Though one of the things I don't like about these various get*Name[s]
methods is that they kind of mix date format stuff into the logic. Another idea would be to have method like getNumMonthsInYear(date: D)
and getNumDaysInMonth(date: D)
we could then get the names of the months by creating a date for the month and doing something like format(date, formats.LONG_MONTH_NAME)
. Changing the API to this would also allow us to support calendars that have a variable number of months depending on the year .
Wouldn't it be clearer to use the term "day"?
I used "date" to stay consistent with what javascript does and avoid confusion with days of the week. Though I agree its confusing in its own way. I would be ok replacing the "date" APIs with "dayOfMonth".
Isn't that somehow redundant? -> getYear(date: D): number
The idea was that the name variant could be used in situations where the string representation was more than just the number. For example in Japanese maybe someone would want to include the year kanji: "2017年". Though as I mentioned above I don't like how these "-name" APIs mix in formatting logic. It might be better to move to something like format(date, formats.YEAR_NAME)
.
Do you want to start a repo for it? If you run into flexibility issues with the DateAdapter
API as you go let me know and I'll consider making changes. I'd also be happy to review code if you need an extra set of eyes on anything.
@mmalerba No worries! Thank you very much for your detailed response.
I have started implementing JDNConvertibleCalendar
here: https://github.com/dhlab-basel/JDNConvertibleCalendar. I still need to figure out how to structure it because I want it to become an npm module.
I also still need to figure out how date manipulations work (increasing and decreasing year, month, and day). I have already added some simple conversion tests. The current implementation will still change a lot, but I hope that there will be a proof of concept soon.
I did not bother with precision yet (every date has an exact start and end so far: day precision), also I did not care about string formats yet. I think a lot of this should be handled in the implementation of DateAdapter
for JDNConvertibleCalendar
. JDNConvertibleCalendar
should only handle the conversion logic and the math used for manipulations (jump to the next month or year when increasing days etc.). Since I work at a university, I will get all the scientific support I need.
In which repo would the implementation of DateAdapter
have to go for JDNConvertibleCalendar
?
Hmm I think its up to you how you want to structure it. You could put it in the same repo but make a separate npm package from the core calendar logic if you want to make the calendar logic available to people who aren't using Angular. The angular specific stuff would probably consist of the date adapter, a set of formats, and a module or 2 that people can import to easily set up the providers.
This is similar to what we do for the MomentDateAdapter
it lives in the same repo as the rest of material, but we publish it as its own npm package since it has additional dependencies.
I think it would make sense to put the JDNConvertibleCalendarDateAdapter
in the Angular Material 2 repo. https://github.com/dhlab-basel/JDNConvertibleCalendar could still be used without Angular Material 2. Also if DateAdapter
is going to be changed in the future, this would make things easier to coordinate.
I forked the Angular Material 2 repo. Shall I implement JDNConvertibleCalendarDateAdapter
there and create a PR? I might have some questions how to include it in app-module.ts
in our Angular app https://github.com/dhlab-basel/Salsah.
How shall I deal with the DateAdapter
interface? Shall I just add what I think is needed as proposed in https://github.com/angular/material2/issues/2519#issuecomment-349264844?
Maybe it would make things easier if we could talk briefly. However, I suppose your are located in the US and I am here in Europe (time diff.).
@mmalerba I created a new branch on my forked repo from angular/material2: https://github.com/dhlab-basel/material2/tree/datepicker-datadapter-jdnconvcal
I am struggling with package.json
and the basic structure (index.ts, tsconfig). I would be glad if you could give me a hand here. I guess once I have the basic setup similar to src/material-moment-adapter
I should be fine and can implement it by myself.
@tobiasschweizer I can set up some time to do a Hangouts conversation if you like. Shoot me an email: mmalerba@google.com
@mmalerba As soon as a build with our merged PR https://github.com/angular/material2/pull/9639 is available, I will make a proof of concept in my repo https://github.com/dhlab-basel/JDNConvertibleCalendarDateAdapter with a custom header so that the user can switch between Gregorian and Julian calendar formats. https://github.com/dhlab-basel/JDNConvertibleCalendar will have to be extended too so that more calendar formats are supported in the future.
I think it is already there https://github.com/angular/material2-builds/commit/d6e71c9c9ee0d54683ee4aa338be634a319fa770
@mmalerba @araselahi
We added support for the Islamic calendar.
See https://stackblitz.com/edit/angular-qapxhn
I set the locale to 'ar', 'en' is used as a fallback for the names of weekdays and months. I think there is still a RTL issue:
Also abbreviations would be nice for transliterations of the Arabic names, see https://github.com/dhlab-basel/JDNConvertibleCalendar/blob/master/src/names.json
Could you give me a hand here?
btw: all the available calendars can also be used individually. In the date adapter, I still have to figure out a way to set the initial calendar correctly, see https://github.com/dhlab-basel/JDNConvertibleCalendarDateAdapter/pull/9
(revisiting old feature requests)
Closing this issue since I don't realistically ever see us prioritizing adding support for non-Gregorian calendars. With the current state of our backlog, it would be years before we would even consider this, and I don't foresee it ever getting near the top of the queue.
(revisiting old feature requests)
Closing this issue since I don't realistically ever see us prioritizing adding support for non-Gregorian calendars. With the current state of our backlog, it would be years before we would even consider this, and I don't foresee it ever getting near the top of the queue.
I understand.
Could you tell me anything about the priority of the precision task (year or month selection)? At the moment, the date and range pickers only allow for day selection.
@tobiasschweizer it's something I'd like to explore eventually, but I don't see us dedicating any time to it in 2021
This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.
I think making dateUtil.js injectable, would provide calendar support functionality for Hijri or Jalali or other international calendars in datepicker. Adding fromGregorian and toGregorian date functions to $mdDateLocale service may provide more abstract solution. These functions could be consequently used in dateUtil.js to provide localized date calculation. Currently $mdDateLocale just provide messages, formatters, and parsers for date internationalization but not date conversion.
I volunteer to produce dateUtil or $mdDateLocale for Hijri or Jalali. Please help me how.