date.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. define(["./has", "./_base/lang"], function(has, lang){
  2. // module:
  3. // dojo/date
  4. var date = {
  5. // summary:
  6. // Date manipulation utilities
  7. };
  8. date.getDaysInMonth = function(/*Date*/dateObject){
  9. // summary:
  10. // Returns the number of days in the month used by dateObject
  11. var month = dateObject.getMonth();
  12. var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  13. if(month == 1 && date.isLeapYear(dateObject)){ return 29; } // Number
  14. return days[month]; // Number
  15. };
  16. date.isLeapYear = function(/*Date*/dateObject){
  17. // summary:
  18. // Determines if the year of the dateObject is a leap year
  19. // description:
  20. // Leap years are years with an additional day YYYY-02-29, where the
  21. // year number is a multiple of four with the following exception: If
  22. // a year is a multiple of 100, then it is only a leap year if it is
  23. // also a multiple of 400. For example, 1900 was not a leap year, but
  24. // 2000 is one.
  25. var year = dateObject.getFullYear();
  26. return !(year%400) || (!(year%4) && !!(year%100)); // Boolean
  27. };
  28. // FIXME: This is not localized
  29. date.getTimezoneName = function(/*Date*/dateObject){
  30. // summary:
  31. // Get the user's time zone as provided by the browser
  32. // dateObject:
  33. // Needed because the timezone may vary with time (daylight savings)
  34. // description:
  35. // Try to get time zone info from toString or toLocaleString method of
  36. // the Date object -- UTC offset is not a time zone. See
  37. // http://www.twinsun.com/tz/tz-link.htm Note: results may be
  38. // inconsistent across browsers.
  39. var str = dateObject.toString(); // Start looking in toString
  40. var tz = ''; // The result -- return empty string if nothing found
  41. var match;
  42. // First look for something in parentheses -- fast lookup, no regex
  43. var pos = str.indexOf('(');
  44. if(pos > -1){
  45. tz = str.substring(++pos, str.indexOf(')'));
  46. }else{
  47. // If at first you don't succeed ...
  48. // If IE knows about the TZ, it appears before the year
  49. // Capital letters or slash before a 4-digit year
  50. // at the end of string
  51. var pat = /([A-Z\/]+) \d{4}$/;
  52. if((match = str.match(pat))){
  53. tz = match[1];
  54. }else{
  55. // Some browsers (e.g. Safari) glue the TZ on the end
  56. // of toLocaleString instead of putting it in toString
  57. str = dateObject.toLocaleString();
  58. // Capital letters or slash -- end of string,
  59. // after space
  60. pat = / ([A-Z\/]+)$/;
  61. if((match = str.match(pat))){
  62. tz = match[1];
  63. }
  64. }
  65. }
  66. // Make sure it doesn't somehow end up return AM or PM
  67. return (tz == 'AM' || tz == 'PM') ? '' : tz; // String
  68. };
  69. // Utility methods to do arithmetic calculations with Dates
  70. date.compare = function(/*Date*/date1, /*Date?*/date2, /*String?*/portion){
  71. // summary:
  72. // Compare two date objects by date, time, or both.
  73. // description:
  74. // Returns 0 if equal, positive if a > b, else negative.
  75. // date1:
  76. // Date object
  77. // date2:
  78. // Date object. If not specified, the current Date is used.
  79. // portion:
  80. // A string indicating the "date" or "time" portion of a Date object.
  81. // Compares both "date" and "time" by default. One of the following:
  82. // "date", "time", "datetime"
  83. // Extra step required in copy for IE - see #3112
  84. date1 = new Date(+date1);
  85. date2 = new Date(+(date2 || new Date()));
  86. if(portion == "date"){
  87. // Ignore times and compare dates.
  88. date1.setHours(0, 0, 0, 0);
  89. date2.setHours(0, 0, 0, 0);
  90. }else if(portion == "time"){
  91. // Ignore dates and compare times.
  92. date1.setFullYear(0, 0, 0);
  93. date2.setFullYear(0, 0, 0);
  94. }
  95. if(date1 > date2){ return 1; } // int
  96. if(date1 < date2){ return -1; } // int
  97. return 0; // int
  98. };
  99. date.add = function(/*Date*/date, /*String*/interval, /*int*/amount){
  100. // summary:
  101. // Add to a Date in intervals of different size, from milliseconds to years
  102. // date: Date
  103. // Date object to start with
  104. // interval:
  105. // A string representing the interval. One of the following:
  106. // "year", "month", "day", "hour", "minute", "second",
  107. // "millisecond", "quarter", "week", "weekday"
  108. // amount:
  109. // How much to add to the date.
  110. var sum = new Date(+date); // convert to Number before copying to accommodate IE (#3112)
  111. var fixOvershoot = false;
  112. var property = "Date";
  113. switch(interval){
  114. case "day":
  115. break;
  116. case "weekday":
  117. //i18n FIXME: assumes Saturday/Sunday weekend, but this is not always true. see dojo/cldr/supplemental
  118. // Divide the increment time span into weekspans plus leftover days
  119. // e.g., 8 days is one 5-day weekspan / and two leftover days
  120. // Can't have zero leftover days, so numbers divisible by 5 get
  121. // a days value of 5, and the remaining days make up the number of weeks
  122. var days, weeks;
  123. var mod = amount % 5;
  124. if(!mod){
  125. days = (amount > 0) ? 5 : -5;
  126. weeks = (amount > 0) ? ((amount-5)/5) : ((amount+5)/5);
  127. }else{
  128. days = mod;
  129. weeks = parseInt(amount/5);
  130. }
  131. // Get weekday value for orig date param
  132. var strt = date.getDay();
  133. // Orig date is Sat / positive incrementer
  134. // Jump over Sun
  135. var adj = 0;
  136. if(strt == 6 && amount > 0){
  137. adj = 1;
  138. }else if(strt == 0 && amount < 0){
  139. // Orig date is Sun / negative incrementer
  140. // Jump back over Sat
  141. adj = -1;
  142. }
  143. // Get weekday val for the new date
  144. var trgt = strt + days;
  145. // New date is on Sat or Sun
  146. if(trgt == 0 || trgt == 6){
  147. adj = (amount > 0) ? 2 : -2;
  148. }
  149. // Increment by number of weeks plus leftover days plus
  150. // weekend adjustments
  151. amount = (7 * weeks) + days + adj;
  152. break;
  153. case "year":
  154. property = "FullYear";
  155. // Keep increment/decrement from 2/29 out of March
  156. fixOvershoot = true;
  157. break;
  158. case "week":
  159. amount *= 7;
  160. break;
  161. case "quarter":
  162. // Naive quarter is just three months
  163. amount *= 3;
  164. // fallthrough...
  165. case "month":
  166. // Reset to last day of month if you overshoot
  167. fixOvershoot = true;
  168. property = "Month";
  169. break;
  170. // case "hour":
  171. // case "minute":
  172. // case "second":
  173. // case "millisecond":
  174. default:
  175. property = "UTC"+interval.charAt(0).toUpperCase() + interval.substring(1) + "s";
  176. }
  177. if(property){
  178. sum["set"+property](sum["get"+property]()+amount);
  179. }
  180. if(fixOvershoot && (sum.getDate() < date.getDate())){
  181. sum.setDate(0);
  182. }
  183. return sum; // Date
  184. };
  185. date.difference = function(/*Date*/date1, /*Date?*/date2, /*String?*/interval){
  186. // summary:
  187. // Get the difference in a specific unit of time (e.g., number of
  188. // months, weeks, days, etc.) between two dates, rounded to the
  189. // nearest integer.
  190. // date1:
  191. // Date object
  192. // date2:
  193. // Date object. If not specified, the current Date is used.
  194. // interval:
  195. // A string representing the interval. One of the following:
  196. // "year", "month", "day", "hour", "minute", "second",
  197. // "millisecond", "quarter", "week", "weekday"
  198. //
  199. // Defaults to "day".
  200. date2 = date2 || new Date();
  201. interval = interval || "day";
  202. var yearDiff = date2.getFullYear() - date1.getFullYear();
  203. var delta = 1; // Integer return value
  204. switch(interval){
  205. case "quarter":
  206. var m1 = date1.getMonth();
  207. var m2 = date2.getMonth();
  208. // Figure out which quarter the months are in
  209. var q1 = Math.floor(m1/3) + 1;
  210. var q2 = Math.floor(m2/3) + 1;
  211. // Add quarters for any year difference between the dates
  212. q2 += (yearDiff * 4);
  213. delta = q2 - q1;
  214. break;
  215. case "weekday":
  216. var days = Math.round(date.difference(date1, date2, "day"));
  217. var weeks = parseInt(date.difference(date1, date2, "week"));
  218. var mod = days % 7;
  219. // Even number of weeks
  220. if(mod == 0){
  221. days = weeks*5;
  222. }else{
  223. // Weeks plus spare change (< 7 days)
  224. var adj = 0;
  225. var aDay = date1.getDay();
  226. var bDay = date2.getDay();
  227. weeks = parseInt(days/7);
  228. mod = days % 7;
  229. // Mark the date advanced by the number of
  230. // round weeks (may be zero)
  231. var dtMark = new Date(date1);
  232. dtMark.setDate(dtMark.getDate()+(weeks*7));
  233. var dayMark = dtMark.getDay();
  234. // Spare change days -- 6 or less
  235. if(days > 0){
  236. switch(true){
  237. // Range starts on Sat
  238. case aDay == 6:
  239. adj = -1;
  240. break;
  241. // Range starts on Sun
  242. case aDay == 0:
  243. adj = 0;
  244. break;
  245. // Range ends on Sat
  246. case bDay == 6:
  247. adj = -1;
  248. break;
  249. // Range ends on Sun
  250. case bDay == 0:
  251. adj = -2;
  252. break;
  253. // Range contains weekend
  254. case (dayMark + mod) > 5:
  255. adj = -2;
  256. }
  257. }else if(days < 0){
  258. switch(true){
  259. // Range starts on Sat
  260. case aDay == 6:
  261. adj = 0;
  262. break;
  263. // Range starts on Sun
  264. case aDay == 0:
  265. adj = 1;
  266. break;
  267. // Range ends on Sat
  268. case bDay == 6:
  269. adj = 2;
  270. break;
  271. // Range ends on Sun
  272. case bDay == 0:
  273. adj = 1;
  274. break;
  275. // Range contains weekend
  276. case (dayMark + mod) < 0:
  277. adj = 2;
  278. }
  279. }
  280. days += adj;
  281. days -= (weeks*2);
  282. }
  283. delta = days;
  284. break;
  285. case "year":
  286. delta = yearDiff;
  287. break;
  288. case "month":
  289. delta = (date2.getMonth() - date1.getMonth()) + (yearDiff * 12);
  290. break;
  291. case "week":
  292. // Truncate instead of rounding
  293. // Don't use Math.floor -- value may be negative
  294. delta = parseInt(date.difference(date1, date2, "day")/7);
  295. break;
  296. case "day":
  297. delta /= 24;
  298. // fallthrough
  299. case "hour":
  300. delta /= 60;
  301. // fallthrough
  302. case "minute":
  303. delta /= 60;
  304. // fallthrough
  305. case "second":
  306. delta /= 1000;
  307. // fallthrough
  308. case "millisecond":
  309. delta *= date2.getTime() - date1.getTime();
  310. }
  311. // Round for fractional values and DST leaps
  312. return Math.round(delta); // Number (integer)
  313. };
  314. // Don't use setObject() because it may overwrite dojo/date/stamp (if that has already been loaded)
  315. has("extend-dojo") && lang.mixin(lang.getObject("dojo.date", true), date);
  316. return date;
  317. });