I have a String date format (e.g. dd/MM/yyyy) and want to convert it to a US-style with the month first (e.g. MM/dd/yyyy), programatically.
The use-case of this is to read some data and determine which format fits best.
This sounds trivially easy, but having actually tried implementing it, my solution seems sub-optimal.
Below is my attempt, including a test.
public class DateSwapperExample
{
private static final char dateFormatDayLetter = 'd', dateFormatMonthLetter = 'M';
/**
* Swaps the Day & Month component in a Date Format, if both are present <br>
* When swapping, ensures the frequency is retained - e.g. dd/MMM -> MMM/dd <br>
* TODO Only handles one instance of each tag <br>
* TODO This doesn't handle quoted elements in the Date Format (e.g. "dd/mm 'since dave made the best cakes' yyyy")
*/
private static String swapDayAndMonthInDateFormat(final String dateFormat)
{
// Get the position of the groups
final int[] dayIndex = new int[] {dateFormat.indexOf(dateFormatDayLetter), dateFormat.lastIndexOf(dateFormatDayLetter)};
final int[] monthIndex = new int[] {dateFormat.indexOf(dateFormatMonthLetter), dateFormat.lastIndexOf(dateFormatMonthLetter)};
if ((dayIndex[0] == -1) || (monthIndex[0] == -1))
{
// Cannot swap as dateFormat does not contain both dateFormatDayLetter & dateFormatMonthLetter
return dateFormat;
}
else
{
final int[] firstGroup, secondGroup;
// Work out which group comes first
if (dayIndex[0] < monthIndex[0])
{
firstGroup = dayIndex;
secondGroup = monthIndex;
}
else
{
firstGroup = monthIndex;
secondGroup = dayIndex;
}
// Split the string up into segments, re-organise and combine
// The other parts of the format at the start
return substringConstrained(dateFormat, 0, firstGroup[0])
// The second group
+ substringConstrained(dateFormat, secondGroup[0], secondGroup[1] + 1)
// The other parts of the format in the middle
+ substringConstrained(dateFormat, firstGroup[1] + 1, secondGroup[0])
// The first group
+ substringConstrained(dateFormat, firstGroup[0], firstGroup[1] + 1)
// The other parts of the format at the end
+ substringConstrained(dateFormat, secondGroup[1] + 1, dateFormat.length());
}
}
/** Extension of {#link String#substring(int, int)} that constrains the index parameters to be within the allowed range */
private static String substringConstrained(final String str, final int beginIndex, final int endIndex)
{
return str.substring(constrainToRange(beginIndex, 0, str.length()), constrainToRange(endIndex, 0, str.length()));
}
/** Copy of {#link com.google.common.primitives.Ints#constrainToRange(int, int, int)} to avoid the need of Guava in this example */
private static int constrainToRange(int value, int min, int max)
{
return Math.min(Math.max(value, min), max);
}
#org.junit.Test
public void testSwapDayAndMonthInDateFormat()
{
org.junit.Assert.assertEquals("Md", swapDayAndMonthInDateFormat("dM"));
org.junit.Assert.assertEquals("MMd", swapDayAndMonthInDateFormat("dMM"));
org.junit.Assert.assertEquals("Mdy", swapDayAndMonthInDateFormat("dMy"));
org.junit.Assert.assertEquals("Myd", swapDayAndMonthInDateFormat("dyM"));
org.junit.Assert.assertEquals("yMd", swapDayAndMonthInDateFormat("ydM"));
org.junit.Assert.assertEquals("aMbdc", swapDayAndMonthInDateFormat("adbMc"));
org.junit.Assert.assertEquals("MM/dd/yyyy", swapDayAndMonthInDateFormat("dd/MM/yyyy"));
org.junit.Assert.assertEquals("MMM/dd/yyyy", swapDayAndMonthInDateFormat("dd/MMM/yyyy"));
for (final String str : new String[] {"ydy", "yMy", "yDy", "ymy", "Dm", "Dmm", "DD/mm/yyyy", "DD/mmm/yyyy"})
{
org.junit.Assert.assertEquals(str, swapDayAndMonthInDateFormat(str));
}
}
}
private static String swapDayAndMonthInDateFormat(final String dateFormat)
{
return dateFormat.replaceFirst("(d+)(.*?)(M+)", "$3$2$1");
}
I am far from convinced that this is the good solution to your real problem. But it makes you test pass.
Also you should not want to use SimpleDateFormat. That class is notoriously troublesome and along with Date and friends long outdated. Instead use DateTimeFormatter and other classes from java.time, the modern Java date and time API. Format pattern strings still look similar, though, so it could be that this answer is still relevant.
Related
This question already has answers here:
Java - Check if array contains 3 consecutive dates
(4 answers)
Closed 5 years ago.
I have an array of unique dates from each time the user completes a task. I want to check if the dates within the array are consecutive from and including todays date.
If the array contains dates: "2017/6/2, 2017/6/3, 2017/6/4, 2017/6/5" then based on today's date being 2017/6/5 the function would return 4 as there are 4 consecutive dates from and including today.
If the array contains dates "2017/6/2, 2017/6/3, 2017/6/4" then it would return 0 as the array does not include today's date. Otherwise the count would be broken upon a non consecutive date.
List<Date> dateList = new ArrayList<Date>();
int count = 0;
Date todayDate = new Date();
for (int i=0; i<dateList.size(); i++){
// Check if dates within the array are consecutive from todayDate, if so then increment count by 1.
}
If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.
If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).
Although you can also use JodaTime, it's being discontinued and replaced by the new APIs, do I don't recommend start a new project with joda. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".
As you want to compare just the date (day/month/year), and not the time (hour/minute/second), the best choice is to use the LocalDate class. For java 8, this class is in java.time package, and in ThreeTen Backport, the package is org.threeten.bp. But the classes and methods names are the same.
The code would be like this:
public int count(List<LocalDate> dateList, LocalDate today) {
if (!dateList.contains(today)) { // today is not in the list, return 0
return 0;
}
int count = 0;
LocalDate prev = dateList.get(0); // get first date from list
for (int i = 1; i < dateList.size(); i++) {
LocalDate next = dateList.get(i);
if (prev.plusDays(1).equals(next)) {
// difference between dates is one day
count++;
} else {
// difference between dates is not 1
// Do what? return 0? throw exception?
}
prev = next;
}
return count + 1; // didn't count the first element, adding 1
}
Testing this method:
List<LocalDate> dateList = new ArrayList<>();
dateList.add(LocalDate.of(2017, 6, 2));
dateList.add(LocalDate.of(2017, 6, 3));
dateList.add(LocalDate.of(2017, 6, 4));
dateList.add(LocalDate.of(2017, 6, 5));
LocalDate today = LocalDate.now();
System.out.println(count(dateList, today)); // 4
Another test (when today is not in the list)
List<LocalDate> dateList = new ArrayList<>();
dateList.add(LocalDate.of(2017, 6, 2));
dateList.add(LocalDate.of(2017, 6, 3));
dateList.add(LocalDate.of(2017, 6, 4));
LocalDate today = LocalDate.now();
System.out.println(count(dateList, today)); // 0
Notes:
As it wasn't specified what to do when the days are not consecutive (return 0 or throw exception), I left this part commented. But it should be straightforward to add this to the code
If you want to convert java.util.Date to LocalDate, you can do as follows (using the code of this answer, full explanation is in this link in case you have any questions):
public LocalDate convert(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
// if your Date has no toInstant method, try this:
public LocalDate convert(Date date) {
return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
}
I understood that you want to check for consecutive days (so, a 1-day difference between the dates). But if you want to check if the previous date is before the next (no matter how many days), you can change the if (prev.plusDays(1).equals(next)) to if (prev.isBefore(next))
I'm not sure if that's the case, but if you want, you can also parse a String directly to a LocalDate (so you don't need to create lots of Date objects), using a DateTimeFormatter:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/d");
LocalDate d = LocalDate.parse("2017/6/2", formatter); // 2017-06-02
There are a lot of ways to write it more clear:
Use new Date API;
Use libraries;
But, in such case, with usage of old Date classes, I would do that in such a way:
public static void main(String[] args) {
long millisInDay = TimeUnit.DAYS.toMillis(1);
List<Date> dates = Arrays.asList(new Date("2017/6/2"), new Date("2017/6/3"), new Date("2017/6/4"), new Date("2017/6/5"));
System.out.println(getSequentialNumber(millisInDay, dates));
}
private static int getSequentialNumber(long millisInDay, List<Date> dates) {
int count = 0;
Date now = setMidnight(Calendar.getInstance().getTime());
for (int i = dates.size() - 1; i >= 0; i--) {
Date date = setMidnight(dates.get(i));
if (date.getTime() == now.getTime()) {
count++;
}
now.setTime(now.getTime() - millisInDay);
}
return count;
}
private static Date setMidnight(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.MILLISECOND, 0);
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.HOUR_OF_DAY, 0);
return calendar.getTime();
}
If I understand the requirement correctly, you have an array of Date objects, ordered by date, and guaranteed not to have two Date objects for the same day, but possibly with gaps between the days. Your goal is to return the length of the maximum sub-array that contains only consecutive days and also includes the current day, or to return 0 if there is no such sub-array. The current day may fall anywhere inside that sub-array, not necessarily at the beginning or end.
It's not clear if you need to support crossing year boundaries, but I'll assume so. I also assume that all the Date objects in the list are for the same time zone which is also the time zone for the device on which you are running. If that's not the case, you should refer to this answer for more information on testing whether two Date objects refer to the same day.
It's fairly simple to do this if you work with Calendar objects instead of Date objects. You don't need any third-party libraries, as both Date and Calendar are parts of the standard Android API. I suggest doing this in two phases: first search for the current date in the array and then scan in both directions for either a gap in the dates or an array boundary. Then just count how far you could go in each direction.
public int getDateSpanCount(List<Date> dateList) {
final int n = dateList.size();
final Calendar today = Calendar.getInstance();
final Calendar other = Calendar.getInstance();
int count = 0;
// First search for today in the date array
int posToday = -1;
for (int i=0; i<n; i++) {
other.setTime(dateList.get(i));
if (areSameDay(today, other)) {
posToday = i;
break;
}
}
// If today is in the list, count the size of the sub-array containing today
if (posToday >= 0) {
count++; // count today, at least
final Calendar probe = Calendar.getInstance();
// scan backwards from position of today's date
for (int prevPos = posToday - 1; prevPos >= 0; prevPos--) {
final Date prev = dateList.get(prevPos);
probe.setTime(prev);
other.add(Calendar.DAY_OF_YEAR, -1);
if (areSameDay(probe, other)) {
count++;
other.setTime(prev);
} else {
break;
}
}
// reset the other time
other.setTime(today.getTime());
// scan forward from position of today's date
for (int nextPos = posToday + 1; nextPos < n; nextPos++) {
final Date next = dateList.get(nextPos);
probe.setTime(next);
other.add(Calendar.DAY_OF_YEAR, 1);
if (areSameDay(probe, other)) {
count++;
other.setTime(next);
} else {
break;
}
}
}
return count;
}
/** Test whether two Calendar objects are set to the same day */
private static boolean areSameDay(Calendar c1, Calendar c2) {
// see discussion above if dates may not all be for the local time zone
return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) &&
c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
}
I'm create a java webapp and I have to create a consecutive number that should starts every month. The idea is to have something like this:
01-0414 / 02-0414 /03-0414 / 04-0414
where the first two digits should be the consecutive number, and the last four digits are the month and year.
I'm using spring 3.2.2 and hibernate 4.2.6. I really appreciate any help about this.
thanks
Well, your question is not clear. But as far as I understand you need help to get the date. You can use Calendar() or Date(), use something like this Calendar.get(Calendar.MONTH)to get the month and year (or simply parse to string and substring where you want).
Regarding the number at the beginning, I will assume (again, since you are not clear) that it is passed as an input. So basically you concatenate that with the "-" and the output of the previous step; the date thingy.
I hope I helped!
If you want to encode your web app string using a sequence number that gets reset to 1 at the beginning of each month, you could use a singleton class instance to hold the month and sequence number state. The code string generator method checks whether the month has changed, and if so, it resets the internal current month to the new month, and resets the effective internal sequence number to 1.
Here is the generator class (see below for an example of how to use it):
public class MySequenceCodeStringGenerator {
private static final int generatorMonth;
private static final int generatorSequenceNumber;
// Create a singleton instance to hold month and sequence number state.
private static final MySequenceCodeStringGenerator INSTANCE = new MySequenceCodeStringGenerator();
private MySequenceCodeStringGenerator() {
generatorMonth = getCurrentMonth();
generatorSequenceNumber = 0;
}
/////////////////////////
// PUBLIC functions:
/////////////////////////
// Get the singleton instance:
public static MySequenceCodeStringGenerator getInstance() {
return INSTANCE;
}
// Get the formatted sequence code string:
public static int getSequenceCodeString {
int sequenceNumber = getSequenceNumber();
Calendar now = Calendar.getInstance();
int year = now.get(Calendar.YEAR);
int month = now.get(Calendar.MONTH);
String yearString = String.valueOf(year);
return String.format( "%02d-%02d%s", sequenceNumber, month+1, yearString.substring(2) );
}
// Get the current month:
private int getCurrentMonth() {
Calendar now = Calendar.getInstance();
return now.get(Calendar.MONTH);
}
// Get the singleton sequence number. Update if this is a new month.
private int getSequenceNumber() {
currentMonth = getCurrentMonth();
if ( currentMonth != generatorMonth ) {
generatorMonth = currentMonth;
generatorSequenceNumber = 0;
}
return ++generatorSequenceNumber;
}
}
Here's an example of how you use the generator class:
String myWebAppString = MySequenceCodeStringGenerator.getInstance().getSequenceCodeString();
I need to append to 2 HSSFRichTextStrings in Java with Apache POI. How can I do this?
What I'm exactly doing is I'm getting the rich text string already present in a cell and I'm trying to append an additional rich text string to it and write it back to the cell.
Please tell me how to do this.
It is possible to append two HSSFRichTextStrings, but you will have to do most of the work yourself. You will need to take advantage of the following methods in HSSFRichTextString:
numFormattingRuns() - Returns the number of formatting runs in the HSFFRichTextString.
getFontOfFormattingRun(int) - Returns the short font index present at the specified position in the string
applyFont(int, int, short) - Applies the font referred to by the short font index between the given start index (inclusive) and end index (exclusive).
First, create a little class to store formatting run stats:
public class FormattingRun {
private int beginIdx;
private int length;
private short fontIdx;
public FormattingRun(int beginIdx, int length, short fontIdx) {
this.beginIdx = beginIdx;
this.length = length;
this.fontIdx = fontIdx;
}
public int getBegin() { return beginIdx; }
public int getLength() { return length; }
public short getFontIndex { return fontIdx; }
}
Next, gather all of the formatting run statistics for each of the two strings. You'll have to walk the strings yourself to determine how long each formatting run lasts.
List<FormattingRun> formattingRuns = new ArrayList<FormattingRun>();
int numFormattingRuns = richTextString.numFormattingRuns();
for (int fmtIdx = 0; fmtIdx < numFormattingRuns; fmtIdx)
{
int begin = richTextString.getIndexOfFormattingRun(fmtIdx);
short fontIndex = richTextString.getFontOfFormattingRun(fmtIdx);
// Walk the string to determine the length of the formatting run.
int length = 0;
for (int j = begin; j < richTextString.length(); j++)
{
short currFontIndex = richTextString.getFontAtIndex(j);
if (currFontIndex == fontIndex)
length++;
else
break;
}
formattingRuns.add(new FormattingRun(begin, length, fontIndex));
}
Next, concatenate the two String values yourself and create the result HSSFRichTextString.
HSSFRichTextString result = new HSSFRichTextString(
richTextString1.getString() + richTextString2.getString());
Last, apply both sets of formatting runs, with the second set of runs being offset by the first string's length.
for (FormattingRun run1 : formattingRuns1)
{
int begin = run1.getBegin();
int end = begin + run1.getLength();
short fontIdx = run1.getFontIndex();
result.applyFont(begin, end, fontIdx);
}
for (FormattingRun run2 : formattingRuns2)
{
// offset by string length 1
int begin = run2.getBegin() + richTextString1.length();
int end = begin + run2.getLength();
short fontIdx = run2.getFontIndex();
result.applyFont(begin, end, fontIdx);
}
That should do it for concatenating HSSFRichTextStrings.
If you ever want to concatenate XSSFRichTextStrings, found in .xlsx files, the process is very similar. One difference is that XSSFRichTextString#getFontOfFormattingRun will return an XSSFFont instead of a short font index. That's okay, because calling applyFont on an XSSFRichTextString takes an XSSFFont anyway. Another difference is that getFontOfFormattingRun may throw a NullPointerException if there is no font applied for the formatting run, which occurs when there is no different font applied than the font that is already there for the CellStyle for the entire Cell.
If you're using XSSFRichTextStrings, you can't directly concatenate two RichTextString.
However, you can indirectly do so by finding the text value of the second RichTextString and then using the append method to append that string value with an applied font (RichText in essence).
XSSFRichTextString rt1 = new XSSFRichTextString("Apache POI is");
rt1.applyFont(plainArial);
XSSFRichTextString rt2 = new XSSFRichTextString(" great!");
rt2.applyFont(boldArial);
String text = rt2.getString();
cell1.setCellValue(rt1.append(text, boldArial));
Source:
enter link description here
For example, I have input parameter this format: "04:00-06:00" or "23:00-24:00". Type of parameter - String.
And in my method I must check, that time range in input parameter NOT before current time. How I can do it?
More details:
input time range: "12:00-15:00"
current time: 16:00.
In this case, method must return false.
Another example:
input time range: "10:30-12:10"
current time: 09:51.
method must return true.
Can you please give me some idea or algorithm? How I can implement this method?
First off, you should probably just learn to use Joda time.
That said, since the times are all zero padded, you can just compare strings lexically.
public static boolean inRange(String time, String range) {
return time.compareTo(range.substring(0, 5)) >= 0
&& time.compareTo(range.substring(6)) <= 0;
}
It's good practice to fail fast on malformed inputs.
private static final Pattern VALID_TIME = Pattern.compile("[012][0-9]:[0-5][0-9]");
private static final Pattern VALID_RANGE = Pattern.compile("[012][0-9]:[0-5][0-9]-[012][0-9]:[0-5][0-9]");
and then put an assert at the top of inRange:
assert VALID_TIME.matcher(time).matches() : time
assert VALID_RANGE.matcher(range).matches() : range
EDIT:
If you really need to represent the current time as a Date, then you should compare it this way:
public final class Range {
/** Inclusive as minutes since midnight */
public final int start, end;
public Range(int start, int end) {
assert end >= start;
}
/** #param time in minutes since midnight */
public boolean contains(int time) {
return start <= time && time <= end;
}
public static Range valueOf(String s) {
assert VALID_RANGE.matcher(s).matches() : s;
return new Range(minutesInDay(s.substring(0, 5)),
minutesInDay(s.substring(6));
}
private static int minutesInDay(String time) {
return Integer.valueOf(time.substring(0, 2)) * 60
+ Integer.valueOf(time.substring(3));
}
}
Use Range.valueOf to convert from a String, convert your Date to a number of minutes since midnight in whatever timezone you like using whatever calendar implementation you like, and then use Range.contains.
Date currentDate = new Date();
Date maxDate;
Date minDate;
//Parse range to two substrings
//parse two substrings to [HH, MM]
//for HH && MM parseInt()
//
minDate= new Date.SetHour(HH); minDate.SetMinute(MM);
//repeat for max date
if(currentDate.Before(maxDate) && currentDate.After(minDate))
{
return true;
}
else
return false;
I've a code to get year, month and day for one of my application.
package com.cera.hyperionUtils;
import java.util.*;
public class HypDate {
public static int curdate(int field)
{
//1. Specify integer 1 for YEAR, 2 for MONTH, 5 DAY_OF_MONTH
Calendar c = new GregorianCalendar();
c.setLenient(true); //Allow overflow
//2. Extract and Return result
if (field == 2) {
field = c.get(Calendar.MONTH) + 1;
}
return c.get(field);
}
public static void main(String[] args)
{
System.out.println(HypDate.curdate(2));
}
}
But when i pass 2 it is giving 0 year and day prints correctly.....Also i was trying to make month as double digit. (like 01 for 1)
Can someone please help me....? (I''m very new to java coding)
Rather than returning these one by one, you may just want to use a SimpleDateFormat to format it.
Say I want a date as year-month-day:
// Necessary imports
import java.text.DateFormat;
import java.text.SimpleDateFormat;
// Declare class and stuff before this
public static String getFormattedDate() {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
return df.format(new Date());
}
public static void main(String[] args) {
System.out.println(getFormattedDate());
}
Outputs 2010-10-29
Edit:
Since you just want the month, you can do this:
public static String getFormattedMonth() {
DateFormat df = new SimpleDateFormat("MM");
return df.format(new Date());
}
if (field == 2) {
field = c.get(Calendar.MONTH) + 1;
}
return c.get(field);
You retrieve the correct month as an index and then use that index to retrieve another field that will be unknown and related in how the constants are saved. Just return the value before, without using a second get.
Maybe you meant
if (field == 2) {
field = Calendar.MONTH;
}
return c.get(field) + 1;
but I don't get why you are redefining that constants instead that use the one already provided..
The problem comes from the fact that when you are getting the month information, you call c.get() twice, which you don't want to do. Instead, you should directly return after you get the first value
//1. Specify integer 1 for YEAR, 2 for MONTH, 5 DAY_OF_MONTH
Calendar c = new GregorianCalendar();
c.setLenient(true); //Allow overflow
//2. Extract and Return result
if (field == Calendar.MONTH) {
return c.get(field) + 1; //because Java months are 0-based
} else {
return c.get(field);
}