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

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