You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DateTimeService.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client;
  17. import java.util.Date;
  18. import com.google.gwt.i18n.client.DateTimeFormat;
  19. import com.google.gwt.i18n.client.LocaleInfo;
  20. import com.vaadin.shared.ui.datefield.Resolution;
  21. /**
  22. * This class provides date/time parsing services to all components on the
  23. * client side.
  24. *
  25. * @author Vaadin Ltd.
  26. *
  27. */
  28. @SuppressWarnings("deprecation")
  29. public class DateTimeService {
  30. private String currentLocale;
  31. private static int[] maxDaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30,
  32. 31, 30, 31 };
  33. /**
  34. * Creates a new date time service with the application default locale.
  35. */
  36. public DateTimeService() {
  37. currentLocale = LocaleService.getDefaultLocale();
  38. }
  39. /**
  40. * Creates a new date time service with a given locale.
  41. *
  42. * @param locale
  43. * e.g. fi, en etc.
  44. * @throws LocaleNotLoadedException
  45. */
  46. public DateTimeService(String locale) throws LocaleNotLoadedException {
  47. setLocale(locale);
  48. }
  49. public void setLocale(String locale) throws LocaleNotLoadedException {
  50. if (LocaleService.getAvailableLocales().contains(locale)) {
  51. currentLocale = locale;
  52. } else {
  53. throw new LocaleNotLoadedException(locale);
  54. }
  55. }
  56. public String getLocale() {
  57. return currentLocale;
  58. }
  59. public String getMonth(int month) {
  60. try {
  61. return LocaleService.getMonthNames(currentLocale)[month];
  62. } catch (final LocaleNotLoadedException e) {
  63. VConsole.error(e);
  64. return null;
  65. }
  66. }
  67. public String getShortMonth(int month) {
  68. try {
  69. return LocaleService.getShortMonthNames(currentLocale)[month];
  70. } catch (final LocaleNotLoadedException e) {
  71. VConsole.error(e);
  72. return null;
  73. }
  74. }
  75. public String getDay(int day) {
  76. try {
  77. return LocaleService.getDayNames(currentLocale)[day];
  78. } catch (final LocaleNotLoadedException e) {
  79. VConsole.error(e);
  80. return null;
  81. }
  82. }
  83. public String getShortDay(int day) {
  84. try {
  85. return LocaleService.getShortDayNames(currentLocale)[day];
  86. } catch (final LocaleNotLoadedException e) {
  87. VConsole.error(e);
  88. return null;
  89. }
  90. }
  91. public int getFirstDayOfWeek() {
  92. try {
  93. return LocaleService.getFirstDayOfWeek(currentLocale);
  94. } catch (final LocaleNotLoadedException e) {
  95. VConsole.error(e);
  96. return 0;
  97. }
  98. }
  99. public boolean isTwelveHourClock() {
  100. try {
  101. return LocaleService.isTwelveHourClock(currentLocale);
  102. } catch (final LocaleNotLoadedException e) {
  103. VConsole.error(e);
  104. return false;
  105. }
  106. }
  107. public String getClockDelimeter() {
  108. try {
  109. return LocaleService.getClockDelimiter(currentLocale);
  110. } catch (final LocaleNotLoadedException e) {
  111. VConsole.error(e);
  112. return ":";
  113. }
  114. }
  115. private static final String[] DEFAULT_AMPM_STRINGS = { "AM", "PM" };
  116. public String[] getAmPmStrings() {
  117. try {
  118. return LocaleService.getAmPmStrings(currentLocale);
  119. } catch (final LocaleNotLoadedException e) {
  120. // TODO can this practically even happen? Should die instead?
  121. VConsole.error("Locale not loaded, using fallback : AM/PM");
  122. VConsole.error(e);
  123. return DEFAULT_AMPM_STRINGS;
  124. }
  125. }
  126. public int getStartWeekDay(Date date) {
  127. final Date dateForFirstOfThisMonth = new Date(date.getYear(),
  128. date.getMonth(), 1);
  129. int firstDay;
  130. try {
  131. firstDay = LocaleService.getFirstDayOfWeek(currentLocale);
  132. } catch (final LocaleNotLoadedException e) {
  133. VConsole.error("Locale not loaded, using fallback 0");
  134. VConsole.error(e);
  135. firstDay = 0;
  136. }
  137. int start = dateForFirstOfThisMonth.getDay() - firstDay;
  138. if (start < 0) {
  139. start = 6;
  140. }
  141. return start;
  142. }
  143. public static void setMilliseconds(Date date, int ms) {
  144. date.setTime(date.getTime() / 1000 * 1000 + ms);
  145. }
  146. public static int getMilliseconds(Date date) {
  147. if (date == null) {
  148. return 0;
  149. }
  150. return (int) (date.getTime() - date.getTime() / 1000 * 1000);
  151. }
  152. public static int getNumberOfDaysInMonth(Date date) {
  153. final int month = date.getMonth();
  154. if (month == 1 && true == isLeapYear(date)) {
  155. return 29;
  156. }
  157. return maxDaysInMonth[month];
  158. }
  159. public static boolean isLeapYear(Date date) {
  160. // Instantiate the date for 1st March of that year
  161. final Date firstMarch = new Date(date.getYear(), 2, 1);
  162. // Go back 1 day
  163. final long firstMarchTime = firstMarch.getTime();
  164. final long lastDayTimeFeb = firstMarchTime - (24 * 60 * 60 * 1000); // NUM_MILLISECS_A_DAY
  165. // Instantiate new Date with this time
  166. final Date febLastDay = new Date(lastDayTimeFeb);
  167. // Check for date in this new instance
  168. return (29 == febLastDay.getDate()) ? true : false;
  169. }
  170. public static boolean isSameDay(Date d1, Date d2) {
  171. return (getDayInt(d1) == getDayInt(d2));
  172. }
  173. public static boolean isInRange(Date date, Date rangeStart, Date rangeEnd,
  174. Resolution resolution) {
  175. Date s;
  176. Date e;
  177. if (rangeStart.after(rangeEnd)) {
  178. s = rangeEnd;
  179. e = rangeStart;
  180. } else {
  181. e = rangeEnd;
  182. s = rangeStart;
  183. }
  184. long start = s.getYear() * 10000000000l;
  185. long end = e.getYear() * 10000000000l;
  186. long target = date.getYear() * 10000000000l;
  187. if (resolution == Resolution.YEAR) {
  188. return (start <= target && end >= target);
  189. }
  190. start += s.getMonth() * 100000000l;
  191. end += e.getMonth() * 100000000l;
  192. target += date.getMonth() * 100000000l;
  193. if (resolution == Resolution.MONTH) {
  194. return (start <= target && end >= target);
  195. }
  196. start += s.getDate() * 1000000l;
  197. end += e.getDate() * 1000000l;
  198. target += date.getDate() * 1000000l;
  199. if (resolution == Resolution.DAY) {
  200. return (start <= target && end >= target);
  201. }
  202. start += s.getHours() * 10000l;
  203. end += e.getHours() * 10000l;
  204. target += date.getHours() * 10000l;
  205. if (resolution == Resolution.HOUR) {
  206. return (start <= target && end >= target);
  207. }
  208. start += s.getMinutes() * 100l;
  209. end += e.getMinutes() * 100l;
  210. target += date.getMinutes() * 100l;
  211. if (resolution == Resolution.MINUTE) {
  212. return (start <= target && end >= target);
  213. }
  214. start += s.getSeconds();
  215. end += e.getSeconds();
  216. target += date.getSeconds();
  217. return (start <= target && end >= target);
  218. }
  219. private static int getDayInt(Date date) {
  220. final int y = date.getYear();
  221. final int m = date.getMonth();
  222. final int d = date.getDate();
  223. return ((y + 1900) * 10000 + m * 100 + d) * 1000000000;
  224. }
  225. /**
  226. * Returns the ISO-8601 week number of the given date.
  227. *
  228. * @param date
  229. * The date for which the week number should be resolved
  230. * @return The ISO-8601 week number for {@literal date}
  231. */
  232. public static int getISOWeekNumber(Date date) {
  233. final long MILLISECONDS_PER_DAY = 24 * 3600 * 1000;
  234. int dayOfWeek = date.getDay(); // 0 == sunday
  235. // ISO 8601 use weeks that start on monday so we use
  236. // mon=1,tue=2,...sun=7;
  237. if (dayOfWeek == 0) {
  238. dayOfWeek = 7;
  239. }
  240. // Find nearest thursday (defines the week in ISO 8601). The week number
  241. // for the nearest thursday is the same as for the target date.
  242. int nearestThursdayDiff = 4 - dayOfWeek; // 4 is thursday
  243. Date nearestThursday = new Date(date.getTime() + nearestThursdayDiff
  244. * MILLISECONDS_PER_DAY);
  245. Date firstOfJanuary = new Date(nearestThursday.getYear(), 0, 1);
  246. long timeDiff = nearestThursday.getTime() - firstOfJanuary.getTime();
  247. int daysSinceFirstOfJanuary = (int) (timeDiff / MILLISECONDS_PER_DAY);
  248. int weekNumber = (daysSinceFirstOfJanuary) / 7 + 1;
  249. return weekNumber;
  250. }
  251. /**
  252. * Check if format contains the month name. If it does we manually convert
  253. * it to the month name since DateTimeFormat.format always uses the current
  254. * locale and will replace the month name wrong if current locale is
  255. * different from the locale set for the DateField.
  256. *
  257. * MMMM is converted into long month name, MMM is converted into short month
  258. * name. '' are added around the name to avoid that DateTimeFormat parses
  259. * the month name as a pattern.
  260. *
  261. * @param date
  262. * The date to convert
  263. * @param formatStr
  264. * The format string that might contain MMM or MMMM
  265. * @param dateTimeService
  266. * Reference to the Vaadin DateTimeService
  267. * @return
  268. */
  269. public String formatDate(Date date, String formatStr) {
  270. /*
  271. * Format month and day names separately when locale for the
  272. * DateTimeService is not the same as the browser locale
  273. */
  274. formatStr = formatMonthNames(date, formatStr);
  275. formatStr = formatDayNames(date, formatStr);
  276. // Format uses the browser locale
  277. DateTimeFormat format = DateTimeFormat.getFormat(formatStr);
  278. String result = format.format(date);
  279. return result;
  280. }
  281. private String formatDayNames(Date date, String formatStr) {
  282. if (formatStr.contains("EEEE")) {
  283. String dayName = getDay(date.getDay());
  284. if (dayName != null) {
  285. /*
  286. * Replace 4 or more E:s with the quoted day name. Also
  287. * concatenate generated string with any other string prepending
  288. * or following the EEEE pattern, i.e. 'EEEE'ta ' becomes 'DAYta
  289. * ' and not 'DAY''ta ', 'ab'EEEE becomes 'abDAY', 'x'EEEE'y'
  290. * becomes 'xDAYy'.
  291. */
  292. formatStr = formatStr.replaceAll("'([E]{4,})'", dayName);
  293. formatStr = formatStr.replaceAll("([E]{4,})'", "'" + dayName);
  294. formatStr = formatStr.replaceAll("'([E]{4,})", dayName + "'");
  295. formatStr = formatStr
  296. .replaceAll("[E]{4,}", "'" + dayName + "'");
  297. }
  298. }
  299. if (formatStr.contains("EEE")) {
  300. String dayName = getShortDay(date.getDay());
  301. if (dayName != null) {
  302. /*
  303. * Replace 3 or more E:s with the quoted month name. Also
  304. * concatenate generated string with any other string prepending
  305. * or following the EEE pattern, i.e. 'EEE'ta ' becomes 'DAYta '
  306. * and not 'DAY''ta ', 'ab'EEE becomes 'abDAY', 'x'EEE'y'
  307. * becomes 'xDAYy'.
  308. */
  309. formatStr = formatStr.replaceAll("'([E]{3,})'", dayName);
  310. formatStr = formatStr.replaceAll("([E]{3,})'", "'" + dayName);
  311. formatStr = formatStr.replaceAll("'([E]{3,})", dayName + "'");
  312. formatStr = formatStr
  313. .replaceAll("[E]{3,}", "'" + dayName + "'");
  314. }
  315. }
  316. return formatStr;
  317. }
  318. private String formatMonthNames(Date date, String formatStr) {
  319. if (formatStr.contains("MMMM")) {
  320. String monthName = getMonth(date.getMonth());
  321. if (monthName != null) {
  322. /*
  323. * Replace 4 or more M:s with the quoted month name. Also
  324. * concatenate generated string with any other string prepending
  325. * or following the MMMM pattern, i.e. 'MMMM'ta ' becomes
  326. * 'MONTHta ' and not 'MONTH''ta ', 'ab'MMMM becomes 'abMONTH',
  327. * 'x'MMMM'y' becomes 'xMONTHy'.
  328. */
  329. formatStr = formatStr.replaceAll("'([M]{4,})'", monthName);
  330. formatStr = formatStr.replaceAll("([M]{4,})'", "'" + monthName);
  331. formatStr = formatStr.replaceAll("'([M]{4,})", monthName + "'");
  332. formatStr = formatStr.replaceAll("[M]{4,}", "'" + monthName
  333. + "'");
  334. }
  335. }
  336. if (formatStr.contains("MMM")) {
  337. String monthName = getShortMonth(date.getMonth());
  338. if (monthName != null) {
  339. /*
  340. * Replace 3 or more M:s with the quoted month name. Also
  341. * concatenate generated string with any other string prepending
  342. * or following the MMM pattern, i.e. 'MMM'ta ' becomes 'MONTHta
  343. * ' and not 'MONTH''ta ', 'ab'MMM becomes 'abMONTH', 'x'MMM'y'
  344. * becomes 'xMONTHy'.
  345. */
  346. formatStr = formatStr.replaceAll("'([M]{3,})'", monthName);
  347. formatStr = formatStr.replaceAll("([M]{3,})'", "'" + monthName);
  348. formatStr = formatStr.replaceAll("'([M]{3,})", monthName + "'");
  349. formatStr = formatStr.replaceAll("[M]{3,}", "'" + monthName
  350. + "'");
  351. }
  352. }
  353. return formatStr;
  354. }
  355. /**
  356. * Replaces month names in the entered date with the name in the current
  357. * browser locale.
  358. *
  359. * @param enteredDate
  360. * Date string e.g. "5 May 2010"
  361. * @param formatString
  362. * Format string e.g. "d M yyyy"
  363. * @return The date string where the month names have been replaced by the
  364. * browser locale version
  365. */
  366. private String parseMonthName(String enteredDate, String formatString) {
  367. LocaleInfo browserLocale = LocaleInfo.getCurrentLocale();
  368. if (browserLocale.getLocaleName().equals(getLocale())) {
  369. // No conversion needs to be done when locales match
  370. return enteredDate;
  371. }
  372. String[] browserMonthNames = browserLocale.getDateTimeConstants()
  373. .months();
  374. String[] browserShortMonthNames = browserLocale.getDateTimeConstants()
  375. .shortMonths();
  376. if (formatString.contains("MMMM")) {
  377. // Full month name
  378. for (int i = 0; i < 12; i++) {
  379. enteredDate = enteredDate.replaceAll(getMonth(i),
  380. browserMonthNames[i]);
  381. }
  382. }
  383. if (formatString.contains("MMM")) {
  384. // Short month name
  385. for (int i = 0; i < 12; i++) {
  386. enteredDate = enteredDate.replaceAll(getShortMonth(i),
  387. browserShortMonthNames[i]);
  388. }
  389. }
  390. return enteredDate;
  391. }
  392. /**
  393. * Parses the given date string using the given format string and the locale
  394. * set in this DateTimeService instance.
  395. *
  396. * @param dateString
  397. * Date string e.g. "1 February 2010"
  398. * @param formatString
  399. * Format string e.g. "d MMMM yyyy"
  400. * @param lenient
  401. * true to use lenient parsing, false to use strict parsing
  402. * @return A Date object representing the dateString. Never returns null.
  403. * @throws IllegalArgumentException
  404. * if the parsing fails
  405. *
  406. */
  407. public Date parseDate(String dateString, String formatString,
  408. boolean lenient) throws IllegalArgumentException {
  409. /* DateTimeFormat uses the browser's locale */
  410. DateTimeFormat format = DateTimeFormat.getFormat(formatString);
  411. /*
  412. * Parse month names separately when locale for the DateTimeService is
  413. * not the same as the browser locale
  414. */
  415. dateString = parseMonthName(dateString, formatString);
  416. Date date;
  417. if (lenient) {
  418. date = format.parse(dateString);
  419. } else {
  420. date = format.parseStrict(dateString);
  421. }
  422. // Some version of Firefox sets the timestamp to 0 if parsing fails.
  423. if (date != null && date.getTime() == 0) {
  424. throw new IllegalArgumentException("Parsing of '" + dateString
  425. + "' failed");
  426. }
  427. return date;
  428. }
  429. }