| @@ 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. |
|