@@ 745-1204 (lines=460) @@ | ||
742 | ||
743 | angular.module('ui.bootstrap.dateparser', []) |
|
744 | ||
745 | .service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) { |
|
746 | // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js |
|
747 | var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; |
|
748 | ||
749 | var localeId; |
|
750 | var formatCodeToRegex; |
|
751 | ||
752 | this.init = function() { |
|
753 | localeId = $locale.id; |
|
754 | ||
755 | this.parsers = {}; |
|
756 | this.formatters = {}; |
|
757 | ||
758 | formatCodeToRegex = [ |
|
759 | { |
|
760 | key: 'yyyy', |
|
761 | regex: '\\d{4}', |
|
762 | apply: function(value) { this.year = +value; }, |
|
763 | formatter: function(date) { |
|
764 | var _date = new Date(); |
|
765 | _date.setFullYear(Math.abs(date.getFullYear())); |
|
766 | return dateFilter(_date, 'yyyy'); |
|
767 | } |
|
768 | }, |
|
769 | { |
|
770 | key: 'yy', |
|
771 | regex: '\\d{2}', |
|
772 | apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; }, |
|
773 | formatter: function(date) { |
|
774 | var _date = new Date(); |
|
775 | _date.setFullYear(Math.abs(date.getFullYear())); |
|
776 | return dateFilter(_date, 'yy'); |
|
777 | } |
|
778 | }, |
|
779 | { |
|
780 | key: 'y', |
|
781 | regex: '\\d{1,4}', |
|
782 | apply: function(value) { this.year = +value; }, |
|
783 | formatter: function(date) { |
|
784 | var _date = new Date(); |
|
785 | _date.setFullYear(Math.abs(date.getFullYear())); |
|
786 | return dateFilter(_date, 'y'); |
|
787 | } |
|
788 | }, |
|
789 | { |
|
790 | key: 'M!', |
|
791 | regex: '0?[1-9]|1[0-2]', |
|
792 | apply: function(value) { this.month = value - 1; }, |
|
793 | formatter: function(date) { |
|
794 | var value = date.getMonth(); |
|
795 | if (/^[0-9]$/.test(value)) { |
|
796 | return dateFilter(date, 'MM'); |
|
797 | } |
|
798 | ||
799 | return dateFilter(date, 'M'); |
|
800 | } |
|
801 | }, |
|
802 | { |
|
803 | key: 'MMMM', |
|
804 | regex: $locale.DATETIME_FORMATS.MONTH.join('|'), |
|
805 | apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }, |
|
806 | formatter: function(date) { return dateFilter(date, 'MMMM'); } |
|
807 | }, |
|
808 | { |
|
809 | key: 'MMM', |
|
810 | regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), |
|
811 | apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }, |
|
812 | formatter: function(date) { return dateFilter(date, 'MMM'); } |
|
813 | }, |
|
814 | { |
|
815 | key: 'MM', |
|
816 | regex: '0[1-9]|1[0-2]', |
|
817 | apply: function(value) { this.month = value - 1; }, |
|
818 | formatter: function(date) { return dateFilter(date, 'MM'); } |
|
819 | }, |
|
820 | { |
|
821 | key: 'M', |
|
822 | regex: '[1-9]|1[0-2]', |
|
823 | apply: function(value) { this.month = value - 1; }, |
|
824 | formatter: function(date) { return dateFilter(date, 'M'); } |
|
825 | }, |
|
826 | { |
|
827 | key: 'd!', |
|
828 | regex: '[0-2]?[0-9]{1}|3[0-1]{1}', |
|
829 | apply: function(value) { this.date = +value; }, |
|
830 | formatter: function(date) { |
|
831 | var value = date.getDate(); |
|
832 | if (/^[1-9]$/.test(value)) { |
|
833 | return dateFilter(date, 'dd'); |
|
834 | } |
|
835 | ||
836 | return dateFilter(date, 'd'); |
|
837 | } |
|
838 | }, |
|
839 | { |
|
840 | key: 'dd', |
|
841 | regex: '[0-2][0-9]{1}|3[0-1]{1}', |
|
842 | apply: function(value) { this.date = +value; }, |
|
843 | formatter: function(date) { return dateFilter(date, 'dd'); } |
|
844 | }, |
|
845 | { |
|
846 | key: 'd', |
|
847 | regex: '[1-2]?[0-9]{1}|3[0-1]{1}', |
|
848 | apply: function(value) { this.date = +value; }, |
|
849 | formatter: function(date) { return dateFilter(date, 'd'); } |
|
850 | }, |
|
851 | { |
|
852 | key: 'EEEE', |
|
853 | regex: $locale.DATETIME_FORMATS.DAY.join('|'), |
|
854 | formatter: function(date) { return dateFilter(date, 'EEEE'); } |
|
855 | }, |
|
856 | { |
|
857 | key: 'EEE', |
|
858 | regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'), |
|
859 | formatter: function(date) { return dateFilter(date, 'EEE'); } |
|
860 | }, |
|
861 | { |
|
862 | key: 'HH', |
|
863 | regex: '(?:0|1)[0-9]|2[0-3]', |
|
864 | apply: function(value) { this.hours = +value; }, |
|
865 | formatter: function(date) { return dateFilter(date, 'HH'); } |
|
866 | }, |
|
867 | { |
|
868 | key: 'hh', |
|
869 | regex: '0[0-9]|1[0-2]', |
|
870 | apply: function(value) { this.hours = +value; }, |
|
871 | formatter: function(date) { return dateFilter(date, 'hh'); } |
|
872 | }, |
|
873 | { |
|
874 | key: 'H', |
|
875 | regex: '1?[0-9]|2[0-3]', |
|
876 | apply: function(value) { this.hours = +value; }, |
|
877 | formatter: function(date) { return dateFilter(date, 'H'); } |
|
878 | }, |
|
879 | { |
|
880 | key: 'h', |
|
881 | regex: '[0-9]|1[0-2]', |
|
882 | apply: function(value) { this.hours = +value; }, |
|
883 | formatter: function(date) { return dateFilter(date, 'h'); } |
|
884 | }, |
|
885 | { |
|
886 | key: 'mm', |
|
887 | regex: '[0-5][0-9]', |
|
888 | apply: function(value) { this.minutes = +value; }, |
|
889 | formatter: function(date) { return dateFilter(date, 'mm'); } |
|
890 | }, |
|
891 | { |
|
892 | key: 'm', |
|
893 | regex: '[0-9]|[1-5][0-9]', |
|
894 | apply: function(value) { this.minutes = +value; }, |
|
895 | formatter: function(date) { return dateFilter(date, 'm'); } |
|
896 | }, |
|
897 | { |
|
898 | key: 'sss', |
|
899 | regex: '[0-9][0-9][0-9]', |
|
900 | apply: function(value) { this.milliseconds = +value; }, |
|
901 | formatter: function(date) { return dateFilter(date, 'sss'); } |
|
902 | }, |
|
903 | { |
|
904 | key: 'ss', |
|
905 | regex: '[0-5][0-9]', |
|
906 | apply: function(value) { this.seconds = +value; }, |
|
907 | formatter: function(date) { return dateFilter(date, 'ss'); } |
|
908 | }, |
|
909 | { |
|
910 | key: 's', |
|
911 | regex: '[0-9]|[1-5][0-9]', |
|
912 | apply: function(value) { this.seconds = +value; }, |
|
913 | formatter: function(date) { return dateFilter(date, 's'); } |
|
914 | }, |
|
915 | { |
|
916 | key: 'a', |
|
917 | regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), |
|
918 | apply: function(value) { |
|
919 | if (this.hours === 12) { |
|
920 | this.hours = 0; |
|
921 | } |
|
922 | ||
923 | if (value === 'PM') { |
|
924 | this.hours += 12; |
|
925 | } |
|
926 | }, |
|
927 | formatter: function(date) { return dateFilter(date, 'a'); } |
|
928 | }, |
|
929 | { |
|
930 | key: 'Z', |
|
931 | regex: '[+-]\\d{4}', |
|
932 | apply: function(value) { |
|
933 | var matches = value.match(/([+-])(\d{2})(\d{2})/), |
|
934 | sign = matches[1], |
|
935 | hours = matches[2], |
|
936 | minutes = matches[3]; |
|
937 | this.hours += toInt(sign + hours); |
|
938 | this.minutes += toInt(sign + minutes); |
|
939 | }, |
|
940 | formatter: function(date) { |
|
941 | return dateFilter(date, 'Z'); |
|
942 | } |
|
943 | }, |
|
944 | { |
|
945 | key: 'ww', |
|
946 | regex: '[0-4][0-9]|5[0-3]', |
|
947 | formatter: function(date) { return dateFilter(date, 'ww'); } |
|
948 | }, |
|
949 | { |
|
950 | key: 'w', |
|
951 | regex: '[0-9]|[1-4][0-9]|5[0-3]', |
|
952 | formatter: function(date) { return dateFilter(date, 'w'); } |
|
953 | }, |
|
954 | { |
|
955 | key: 'GGGG', |
|
956 | regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'), |
|
957 | formatter: function(date) { return dateFilter(date, 'GGGG'); } |
|
958 | }, |
|
959 | { |
|
960 | key: 'GGG', |
|
961 | regex: $locale.DATETIME_FORMATS.ERAS.join('|'), |
|
962 | formatter: function(date) { return dateFilter(date, 'GGG'); } |
|
963 | }, |
|
964 | { |
|
965 | key: 'GG', |
|
966 | regex: $locale.DATETIME_FORMATS.ERAS.join('|'), |
|
967 | formatter: function(date) { return dateFilter(date, 'GG'); } |
|
968 | }, |
|
969 | { |
|
970 | key: 'G', |
|
971 | regex: $locale.DATETIME_FORMATS.ERAS.join('|'), |
|
972 | formatter: function(date) { return dateFilter(date, 'G'); } |
|
973 | } |
|
974 | ]; |
|
975 | }; |
|
976 | ||
977 | this.init(); |
|
978 | ||
979 | function createParser(format, func) { |
|
980 | var map = [], regex = format.split(''); |
|
981 | ||
982 | // check for literal values |
|
983 | var quoteIndex = format.indexOf('\''); |
|
984 | if (quoteIndex > -1) { |
|
985 | var inLiteral = false; |
|
986 | format = format.split(''); |
|
987 | for (var i = quoteIndex; i < format.length; i++) { |
|
988 | if (inLiteral) { |
|
989 | if (format[i] === '\'') { |
|
990 | if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote |
|
991 | format[i+1] = '$'; |
|
992 | regex[i+1] = ''; |
|
993 | } else { // end of literal |
|
994 | regex[i] = ''; |
|
995 | inLiteral = false; |
|
996 | } |
|
997 | } |
|
998 | format[i] = '$'; |
|
999 | } else { |
|
1000 | if (format[i] === '\'') { // start of literal |
|
1001 | format[i] = '$'; |
|
1002 | regex[i] = ''; |
|
1003 | inLiteral = true; |
|
1004 | } |
|
1005 | } |
|
1006 | } |
|
1007 | ||
1008 | format = format.join(''); |
|
1009 | } |
|
1010 | ||
1011 | angular.forEach(formatCodeToRegex, function(data) { |
|
1012 | var index = format.indexOf(data.key); |
|
1013 | ||
1014 | if (index > -1) { |
|
1015 | format = format.split(''); |
|
1016 | ||
1017 | regex[index] = '(' + data.regex + ')'; |
|
1018 | format[index] = '$'; // Custom symbol to define consumed part of format |
|
1019 | for (var i = index + 1, n = index + data.key.length; i < n; i++) { |
|
1020 | regex[i] = ''; |
|
1021 | format[i] = '$'; |
|
1022 | } |
|
1023 | format = format.join(''); |
|
1024 | ||
1025 | map.push({ |
|
1026 | index: index, |
|
1027 | key: data.key, |
|
1028 | apply: data[func], |
|
1029 | matcher: data.regex |
|
1030 | }); |
|
1031 | } |
|
1032 | }); |
|
1033 | ||
1034 | return { |
|
1035 | regex: new RegExp('^' + regex.join('') + '$'), |
|
1036 | map: orderByFilter(map, 'index') |
|
1037 | }; |
|
1038 | } |
|
1039 | ||
1040 | this.filter = function(date, format) { |
|
1041 | if (!angular.isDate(date) || isNaN(date) || !format) { |
|
1042 | return ''; |
|
1043 | } |
|
1044 | ||
1045 | format = $locale.DATETIME_FORMATS[format] || format; |
|
1046 | ||
1047 | if ($locale.id !== localeId) { |
|
1048 | this.init(); |
|
1049 | } |
|
1050 | ||
1051 | if (!this.formatters[format]) { |
|
1052 | this.formatters[format] = createParser(format, 'formatter'); |
|
1053 | } |
|
1054 | ||
1055 | var parser = this.formatters[format], |
|
1056 | map = parser.map; |
|
1057 | ||
1058 | var _format = format; |
|
1059 | ||
1060 | return map.reduce(function(str, mapper, i) { |
|
1061 | var match = _format.match(new RegExp('(.*)' + mapper.key)); |
|
1062 | if (match && angular.isString(match[1])) { |
|
1063 | str += match[1]; |
|
1064 | _format = _format.replace(match[1] + mapper.key, ''); |
|
1065 | } |
|
1066 | ||
1067 | var endStr = i === map.length - 1 ? _format : ''; |
|
1068 | ||
1069 | if (mapper.apply) { |
|
1070 | return str + mapper.apply.call(null, date) + endStr; |
|
1071 | } |
|
1072 | ||
1073 | return str + endStr; |
|
1074 | }, ''); |
|
1075 | }; |
|
1076 | ||
1077 | this.parse = function(input, format, baseDate) { |
|
1078 | if (!angular.isString(input) || !format) { |
|
1079 | return input; |
|
1080 | } |
|
1081 | ||
1082 | format = $locale.DATETIME_FORMATS[format] || format; |
|
1083 | format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); |
|
1084 | ||
1085 | if ($locale.id !== localeId) { |
|
1086 | this.init(); |
|
1087 | } |
|
1088 | ||
1089 | if (!this.parsers[format]) { |
|
1090 | this.parsers[format] = createParser(format, 'apply'); |
|
1091 | } |
|
1092 | ||
1093 | var parser = this.parsers[format], |
|
1094 | regex = parser.regex, |
|
1095 | map = parser.map, |
|
1096 | results = input.match(regex), |
|
1097 | tzOffset = false; |
|
1098 | if (results && results.length) { |
|
1099 | var fields, dt; |
|
1100 | if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { |
|
1101 | fields = { |
|
1102 | year: baseDate.getFullYear(), |
|
1103 | month: baseDate.getMonth(), |
|
1104 | date: baseDate.getDate(), |
|
1105 | hours: baseDate.getHours(), |
|
1106 | minutes: baseDate.getMinutes(), |
|
1107 | seconds: baseDate.getSeconds(), |
|
1108 | milliseconds: baseDate.getMilliseconds() |
|
1109 | }; |
|
1110 | } else { |
|
1111 | if (baseDate) { |
|
1112 | $log.warn('dateparser:', 'baseDate is not a valid date'); |
|
1113 | } |
|
1114 | fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; |
|
1115 | } |
|
1116 | ||
1117 | for (var i = 1, n = results.length; i < n; i++) { |
|
1118 | var mapper = map[i - 1]; |
|
1119 | if (mapper.matcher === 'Z') { |
|
1120 | tzOffset = true; |
|
1121 | } |
|
1122 | ||
1123 | if (mapper.apply) { |
|
1124 | mapper.apply.call(fields, results[i]); |
|
1125 | } |
|
1126 | } |
|
1127 | ||
1128 | var datesetter = tzOffset ? Date.prototype.setUTCFullYear : |
|
1129 | Date.prototype.setFullYear; |
|
1130 | var timesetter = tzOffset ? Date.prototype.setUTCHours : |
|
1131 | Date.prototype.setHours; |
|
1132 | ||
1133 | if (isValid(fields.year, fields.month, fields.date)) { |
|
1134 | if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) { |
|
1135 | dt = new Date(baseDate); |
|
1136 | datesetter.call(dt, fields.year, fields.month, fields.date); |
|
1137 | timesetter.call(dt, fields.hours, fields.minutes, |
|
1138 | fields.seconds, fields.milliseconds); |
|
1139 | } else { |
|
1140 | dt = new Date(0); |
|
1141 | datesetter.call(dt, fields.year, fields.month, fields.date); |
|
1142 | timesetter.call(dt, fields.hours || 0, fields.minutes || 0, |
|
1143 | fields.seconds || 0, fields.milliseconds || 0); |
|
1144 | } |
|
1145 | } |
|
1146 | ||
1147 | return dt; |
|
1148 | } |
|
1149 | }; |
|
1150 | ||
1151 | // Check if date is valid for specific month (and year for February). |
|
1152 | // Month: 0 = Jan, 1 = Feb, etc |
|
1153 | function isValid(year, month, date) { |
|
1154 | if (date < 1) { |
|
1155 | return false; |
|
1156 | } |
|
1157 | ||
1158 | if (month === 1 && date > 28) { |
|
1159 | return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0); |
|
1160 | } |
|
1161 | ||
1162 | if (month === 3 || month === 5 || month === 8 || month === 10) { |
|
1163 | return date < 31; |
|
1164 | } |
|
1165 | ||
1166 | return true; |
|
1167 | } |
|
1168 | ||
1169 | function toInt(str) { |
|
1170 | return parseInt(str, 10); |
|
1171 | } |
|
1172 | ||
1173 | this.toTimezone = toTimezone; |
|
1174 | this.fromTimezone = fromTimezone; |
|
1175 | this.timezoneToOffset = timezoneToOffset; |
|
1176 | this.addDateMinutes = addDateMinutes; |
|
1177 | this.convertTimezoneToLocal = convertTimezoneToLocal; |
|
1178 | ||
1179 | function toTimezone(date, timezone) { |
|
1180 | return date && timezone ? convertTimezoneToLocal(date, timezone) : date; |
|
1181 | } |
|
1182 | ||
1183 | function fromTimezone(date, timezone) { |
|
1184 | return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date; |
|
1185 | } |
|
1186 | ||
1187 | //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207 |
|
1188 | function timezoneToOffset(timezone, fallback) { |
|
1189 | var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; |
|
1190 | return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; |
|
1191 | } |
|
1192 | ||
1193 | function addDateMinutes(date, minutes) { |
|
1194 | date = new Date(date.getTime()); |
|
1195 | date.setMinutes(date.getMinutes() + minutes); |
|
1196 | return date; |
|
1197 | } |
|
1198 | ||
1199 | function convertTimezoneToLocal(date, timezone, reverse) { |
|
1200 | reverse = reverse ? -1 : 1; |
|
1201 | var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); |
|
1202 | return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); |
|
1203 | } |
|
1204 | }]); |
|
1205 | ||
1206 | // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to |
|
1207 | // at most one element. |
@@ 744-1203 (lines=460) @@ | ||
741 | ||
742 | angular.module('ui.bootstrap.dateparser', []) |
|
743 | ||
744 | .service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) { |
|
745 | // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js |
|
746 | var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; |
|
747 | ||
748 | var localeId; |
|
749 | var formatCodeToRegex; |
|
750 | ||
751 | this.init = function() { |
|
752 | localeId = $locale.id; |
|
753 | ||
754 | this.parsers = {}; |
|
755 | this.formatters = {}; |
|
756 | ||
757 | formatCodeToRegex = [ |
|
758 | { |
|
759 | key: 'yyyy', |
|
760 | regex: '\\d{4}', |
|
761 | apply: function(value) { this.year = +value; }, |
|
762 | formatter: function(date) { |
|
763 | var _date = new Date(); |
|
764 | _date.setFullYear(Math.abs(date.getFullYear())); |
|
765 | return dateFilter(_date, 'yyyy'); |
|
766 | } |
|
767 | }, |
|
768 | { |
|
769 | key: 'yy', |
|
770 | regex: '\\d{2}', |
|
771 | apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; }, |
|
772 | formatter: function(date) { |
|
773 | var _date = new Date(); |
|
774 | _date.setFullYear(Math.abs(date.getFullYear())); |
|
775 | return dateFilter(_date, 'yy'); |
|
776 | } |
|
777 | }, |
|
778 | { |
|
779 | key: 'y', |
|
780 | regex: '\\d{1,4}', |
|
781 | apply: function(value) { this.year = +value; }, |
|
782 | formatter: function(date) { |
|
783 | var _date = new Date(); |
|
784 | _date.setFullYear(Math.abs(date.getFullYear())); |
|
785 | return dateFilter(_date, 'y'); |
|
786 | } |
|
787 | }, |
|
788 | { |
|
789 | key: 'M!', |
|
790 | regex: '0?[1-9]|1[0-2]', |
|
791 | apply: function(value) { this.month = value - 1; }, |
|
792 | formatter: function(date) { |
|
793 | var value = date.getMonth(); |
|
794 | if (/^[0-9]$/.test(value)) { |
|
795 | return dateFilter(date, 'MM'); |
|
796 | } |
|
797 | ||
798 | return dateFilter(date, 'M'); |
|
799 | } |
|
800 | }, |
|
801 | { |
|
802 | key: 'MMMM', |
|
803 | regex: $locale.DATETIME_FORMATS.MONTH.join('|'), |
|
804 | apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }, |
|
805 | formatter: function(date) { return dateFilter(date, 'MMMM'); } |
|
806 | }, |
|
807 | { |
|
808 | key: 'MMM', |
|
809 | regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), |
|
810 | apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }, |
|
811 | formatter: function(date) { return dateFilter(date, 'MMM'); } |
|
812 | }, |
|
813 | { |
|
814 | key: 'MM', |
|
815 | regex: '0[1-9]|1[0-2]', |
|
816 | apply: function(value) { this.month = value - 1; }, |
|
817 | formatter: function(date) { return dateFilter(date, 'MM'); } |
|
818 | }, |
|
819 | { |
|
820 | key: 'M', |
|
821 | regex: '[1-9]|1[0-2]', |
|
822 | apply: function(value) { this.month = value - 1; }, |
|
823 | formatter: function(date) { return dateFilter(date, 'M'); } |
|
824 | }, |
|
825 | { |
|
826 | key: 'd!', |
|
827 | regex: '[0-2]?[0-9]{1}|3[0-1]{1}', |
|
828 | apply: function(value) { this.date = +value; }, |
|
829 | formatter: function(date) { |
|
830 | var value = date.getDate(); |
|
831 | if (/^[1-9]$/.test(value)) { |
|
832 | return dateFilter(date, 'dd'); |
|
833 | } |
|
834 | ||
835 | return dateFilter(date, 'd'); |
|
836 | } |
|
837 | }, |
|
838 | { |
|
839 | key: 'dd', |
|
840 | regex: '[0-2][0-9]{1}|3[0-1]{1}', |
|
841 | apply: function(value) { this.date = +value; }, |
|
842 | formatter: function(date) { return dateFilter(date, 'dd'); } |
|
843 | }, |
|
844 | { |
|
845 | key: 'd', |
|
846 | regex: '[1-2]?[0-9]{1}|3[0-1]{1}', |
|
847 | apply: function(value) { this.date = +value; }, |
|
848 | formatter: function(date) { return dateFilter(date, 'd'); } |
|
849 | }, |
|
850 | { |
|
851 | key: 'EEEE', |
|
852 | regex: $locale.DATETIME_FORMATS.DAY.join('|'), |
|
853 | formatter: function(date) { return dateFilter(date, 'EEEE'); } |
|
854 | }, |
|
855 | { |
|
856 | key: 'EEE', |
|
857 | regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'), |
|
858 | formatter: function(date) { return dateFilter(date, 'EEE'); } |
|
859 | }, |
|
860 | { |
|
861 | key: 'HH', |
|
862 | regex: '(?:0|1)[0-9]|2[0-3]', |
|
863 | apply: function(value) { this.hours = +value; }, |
|
864 | formatter: function(date) { return dateFilter(date, 'HH'); } |
|
865 | }, |
|
866 | { |
|
867 | key: 'hh', |
|
868 | regex: '0[0-9]|1[0-2]', |
|
869 | apply: function(value) { this.hours = +value; }, |
|
870 | formatter: function(date) { return dateFilter(date, 'hh'); } |
|
871 | }, |
|
872 | { |
|
873 | key: 'H', |
|
874 | regex: '1?[0-9]|2[0-3]', |
|
875 | apply: function(value) { this.hours = +value; }, |
|
876 | formatter: function(date) { return dateFilter(date, 'H'); } |
|
877 | }, |
|
878 | { |
|
879 | key: 'h', |
|
880 | regex: '[0-9]|1[0-2]', |
|
881 | apply: function(value) { this.hours = +value; }, |
|
882 | formatter: function(date) { return dateFilter(date, 'h'); } |
|
883 | }, |
|
884 | { |
|
885 | key: 'mm', |
|
886 | regex: '[0-5][0-9]', |
|
887 | apply: function(value) { this.minutes = +value; }, |
|
888 | formatter: function(date) { return dateFilter(date, 'mm'); } |
|
889 | }, |
|
890 | { |
|
891 | key: 'm', |
|
892 | regex: '[0-9]|[1-5][0-9]', |
|
893 | apply: function(value) { this.minutes = +value; }, |
|
894 | formatter: function(date) { return dateFilter(date, 'm'); } |
|
895 | }, |
|
896 | { |
|
897 | key: 'sss', |
|
898 | regex: '[0-9][0-9][0-9]', |
|
899 | apply: function(value) { this.milliseconds = +value; }, |
|
900 | formatter: function(date) { return dateFilter(date, 'sss'); } |
|
901 | }, |
|
902 | { |
|
903 | key: 'ss', |
|
904 | regex: '[0-5][0-9]', |
|
905 | apply: function(value) { this.seconds = +value; }, |
|
906 | formatter: function(date) { return dateFilter(date, 'ss'); } |
|
907 | }, |
|
908 | { |
|
909 | key: 's', |
|
910 | regex: '[0-9]|[1-5][0-9]', |
|
911 | apply: function(value) { this.seconds = +value; }, |
|
912 | formatter: function(date) { return dateFilter(date, 's'); } |
|
913 | }, |
|
914 | { |
|
915 | key: 'a', |
|
916 | regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), |
|
917 | apply: function(value) { |
|
918 | if (this.hours === 12) { |
|
919 | this.hours = 0; |
|
920 | } |
|
921 | ||
922 | if (value === 'PM') { |
|
923 | this.hours += 12; |
|
924 | } |
|
925 | }, |
|
926 | formatter: function(date) { return dateFilter(date, 'a'); } |
|
927 | }, |
|
928 | { |
|
929 | key: 'Z', |
|
930 | regex: '[+-]\\d{4}', |
|
931 | apply: function(value) { |
|
932 | var matches = value.match(/([+-])(\d{2})(\d{2})/), |
|
933 | sign = matches[1], |
|
934 | hours = matches[2], |
|
935 | minutes = matches[3]; |
|
936 | this.hours += toInt(sign + hours); |
|
937 | this.minutes += toInt(sign + minutes); |
|
938 | }, |
|
939 | formatter: function(date) { |
|
940 | return dateFilter(date, 'Z'); |
|
941 | } |
|
942 | }, |
|
943 | { |
|
944 | key: 'ww', |
|
945 | regex: '[0-4][0-9]|5[0-3]', |
|
946 | formatter: function(date) { return dateFilter(date, 'ww'); } |
|
947 | }, |
|
948 | { |
|
949 | key: 'w', |
|
950 | regex: '[0-9]|[1-4][0-9]|5[0-3]', |
|
951 | formatter: function(date) { return dateFilter(date, 'w'); } |
|
952 | }, |
|
953 | { |
|
954 | key: 'GGGG', |
|
955 | regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'), |
|
956 | formatter: function(date) { return dateFilter(date, 'GGGG'); } |
|
957 | }, |
|
958 | { |
|
959 | key: 'GGG', |
|
960 | regex: $locale.DATETIME_FORMATS.ERAS.join('|'), |
|
961 | formatter: function(date) { return dateFilter(date, 'GGG'); } |
|
962 | }, |
|
963 | { |
|
964 | key: 'GG', |
|
965 | regex: $locale.DATETIME_FORMATS.ERAS.join('|'), |
|
966 | formatter: function(date) { return dateFilter(date, 'GG'); } |
|
967 | }, |
|
968 | { |
|
969 | key: 'G', |
|
970 | regex: $locale.DATETIME_FORMATS.ERAS.join('|'), |
|
971 | formatter: function(date) { return dateFilter(date, 'G'); } |
|
972 | } |
|
973 | ]; |
|
974 | }; |
|
975 | ||
976 | this.init(); |
|
977 | ||
978 | function createParser(format, func) { |
|
979 | var map = [], regex = format.split(''); |
|
980 | ||
981 | // check for literal values |
|
982 | var quoteIndex = format.indexOf('\''); |
|
983 | if (quoteIndex > -1) { |
|
984 | var inLiteral = false; |
|
985 | format = format.split(''); |
|
986 | for (var i = quoteIndex; i < format.length; i++) { |
|
987 | if (inLiteral) { |
|
988 | if (format[i] === '\'') { |
|
989 | if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote |
|
990 | format[i+1] = '$'; |
|
991 | regex[i+1] = ''; |
|
992 | } else { // end of literal |
|
993 | regex[i] = ''; |
|
994 | inLiteral = false; |
|
995 | } |
|
996 | } |
|
997 | format[i] = '$'; |
|
998 | } else { |
|
999 | if (format[i] === '\'') { // start of literal |
|
1000 | format[i] = '$'; |
|
1001 | regex[i] = ''; |
|
1002 | inLiteral = true; |
|
1003 | } |
|
1004 | } |
|
1005 | } |
|
1006 | ||
1007 | format = format.join(''); |
|
1008 | } |
|
1009 | ||
1010 | angular.forEach(formatCodeToRegex, function(data) { |
|
1011 | var index = format.indexOf(data.key); |
|
1012 | ||
1013 | if (index > -1) { |
|
1014 | format = format.split(''); |
|
1015 | ||
1016 | regex[index] = '(' + data.regex + ')'; |
|
1017 | format[index] = '$'; // Custom symbol to define consumed part of format |
|
1018 | for (var i = index + 1, n = index + data.key.length; i < n; i++) { |
|
1019 | regex[i] = ''; |
|
1020 | format[i] = '$'; |
|
1021 | } |
|
1022 | format = format.join(''); |
|
1023 | ||
1024 | map.push({ |
|
1025 | index: index, |
|
1026 | key: data.key, |
|
1027 | apply: data[func], |
|
1028 | matcher: data.regex |
|
1029 | }); |
|
1030 | } |
|
1031 | }); |
|
1032 | ||
1033 | return { |
|
1034 | regex: new RegExp('^' + regex.join('') + '$'), |
|
1035 | map: orderByFilter(map, 'index') |
|
1036 | }; |
|
1037 | } |
|
1038 | ||
1039 | this.filter = function(date, format) { |
|
1040 | if (!angular.isDate(date) || isNaN(date) || !format) { |
|
1041 | return ''; |
|
1042 | } |
|
1043 | ||
1044 | format = $locale.DATETIME_FORMATS[format] || format; |
|
1045 | ||
1046 | if ($locale.id !== localeId) { |
|
1047 | this.init(); |
|
1048 | } |
|
1049 | ||
1050 | if (!this.formatters[format]) { |
|
1051 | this.formatters[format] = createParser(format, 'formatter'); |
|
1052 | } |
|
1053 | ||
1054 | var parser = this.formatters[format], |
|
1055 | map = parser.map; |
|
1056 | ||
1057 | var _format = format; |
|
1058 | ||
1059 | return map.reduce(function(str, mapper, i) { |
|
1060 | var match = _format.match(new RegExp('(.*)' + mapper.key)); |
|
1061 | if (match && angular.isString(match[1])) { |
|
1062 | str += match[1]; |
|
1063 | _format = _format.replace(match[1] + mapper.key, ''); |
|
1064 | } |
|
1065 | ||
1066 | var endStr = i === map.length - 1 ? _format : ''; |
|
1067 | ||
1068 | if (mapper.apply) { |
|
1069 | return str + mapper.apply.call(null, date) + endStr; |
|
1070 | } |
|
1071 | ||
1072 | return str + endStr; |
|
1073 | }, ''); |
|
1074 | }; |
|
1075 | ||
1076 | this.parse = function(input, format, baseDate) { |
|
1077 | if (!angular.isString(input) || !format) { |
|
1078 | return input; |
|
1079 | } |
|
1080 | ||
1081 | format = $locale.DATETIME_FORMATS[format] || format; |
|
1082 | format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); |
|
1083 | ||
1084 | if ($locale.id !== localeId) { |
|
1085 | this.init(); |
|
1086 | } |
|
1087 | ||
1088 | if (!this.parsers[format]) { |
|
1089 | this.parsers[format] = createParser(format, 'apply'); |
|
1090 | } |
|
1091 | ||
1092 | var parser = this.parsers[format], |
|
1093 | regex = parser.regex, |
|
1094 | map = parser.map, |
|
1095 | results = input.match(regex), |
|
1096 | tzOffset = false; |
|
1097 | if (results && results.length) { |
|
1098 | var fields, dt; |
|
1099 | if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { |
|
1100 | fields = { |
|
1101 | year: baseDate.getFullYear(), |
|
1102 | month: baseDate.getMonth(), |
|
1103 | date: baseDate.getDate(), |
|
1104 | hours: baseDate.getHours(), |
|
1105 | minutes: baseDate.getMinutes(), |
|
1106 | seconds: baseDate.getSeconds(), |
|
1107 | milliseconds: baseDate.getMilliseconds() |
|
1108 | }; |
|
1109 | } else { |
|
1110 | if (baseDate) { |
|
1111 | $log.warn('dateparser:', 'baseDate is not a valid date'); |
|
1112 | } |
|
1113 | fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; |
|
1114 | } |
|
1115 | ||
1116 | for (var i = 1, n = results.length; i < n; i++) { |
|
1117 | var mapper = map[i - 1]; |
|
1118 | if (mapper.matcher === 'Z') { |
|
1119 | tzOffset = true; |
|
1120 | } |
|
1121 | ||
1122 | if (mapper.apply) { |
|
1123 | mapper.apply.call(fields, results[i]); |
|
1124 | } |
|
1125 | } |
|
1126 | ||
1127 | var datesetter = tzOffset ? Date.prototype.setUTCFullYear : |
|
1128 | Date.prototype.setFullYear; |
|
1129 | var timesetter = tzOffset ? Date.prototype.setUTCHours : |
|
1130 | Date.prototype.setHours; |
|
1131 | ||
1132 | if (isValid(fields.year, fields.month, fields.date)) { |
|
1133 | if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) { |
|
1134 | dt = new Date(baseDate); |
|
1135 | datesetter.call(dt, fields.year, fields.month, fields.date); |
|
1136 | timesetter.call(dt, fields.hours, fields.minutes, |
|
1137 | fields.seconds, fields.milliseconds); |
|
1138 | } else { |
|
1139 | dt = new Date(0); |
|
1140 | datesetter.call(dt, fields.year, fields.month, fields.date); |
|
1141 | timesetter.call(dt, fields.hours || 0, fields.minutes || 0, |
|
1142 | fields.seconds || 0, fields.milliseconds || 0); |
|
1143 | } |
|
1144 | } |
|
1145 | ||
1146 | return dt; |
|
1147 | } |
|
1148 | }; |
|
1149 | ||
1150 | // Check if date is valid for specific month (and year for February). |
|
1151 | // Month: 0 = Jan, 1 = Feb, etc |
|
1152 | function isValid(year, month, date) { |
|
1153 | if (date < 1) { |
|
1154 | return false; |
|
1155 | } |
|
1156 | ||
1157 | if (month === 1 && date > 28) { |
|
1158 | return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0); |
|
1159 | } |
|
1160 | ||
1161 | if (month === 3 || month === 5 || month === 8 || month === 10) { |
|
1162 | return date < 31; |
|
1163 | } |
|
1164 | ||
1165 | return true; |
|
1166 | } |
|
1167 | ||
1168 | function toInt(str) { |
|
1169 | return parseInt(str, 10); |
|
1170 | } |
|
1171 | ||
1172 | this.toTimezone = toTimezone; |
|
1173 | this.fromTimezone = fromTimezone; |
|
1174 | this.timezoneToOffset = timezoneToOffset; |
|
1175 | this.addDateMinutes = addDateMinutes; |
|
1176 | this.convertTimezoneToLocal = convertTimezoneToLocal; |
|
1177 | ||
1178 | function toTimezone(date, timezone) { |
|
1179 | return date && timezone ? convertTimezoneToLocal(date, timezone) : date; |
|
1180 | } |
|
1181 | ||
1182 | function fromTimezone(date, timezone) { |
|
1183 | return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date; |
|
1184 | } |
|
1185 | ||
1186 | //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207 |
|
1187 | function timezoneToOffset(timezone, fallback) { |
|
1188 | var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; |
|
1189 | return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; |
|
1190 | } |
|
1191 | ||
1192 | function addDateMinutes(date, minutes) { |
|
1193 | date = new Date(date.getTime()); |
|
1194 | date.setMinutes(date.getMinutes() + minutes); |
|
1195 | return date; |
|
1196 | } |
|
1197 | ||
1198 | function convertTimezoneToLocal(date, timezone, reverse) { |
|
1199 | reverse = reverse ? -1 : 1; |
|
1200 | var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); |
|
1201 | return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); |
|
1202 | } |
|
1203 | }]); |
|
1204 | ||
1205 | // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to |
|
1206 | // at most one element. |