Search code examples
androidcalendarsimpledateformat

Calendar won't return date with AM/PM


I want the date in AM/PM format. Examples suggest to use a SimpleDateFormat with a or aa but this is simply not working for me.

First I set the day. Then I set view to visible for start time.

Result is format of Wed May 20 01:00:00 EDT 2020 want something like Wed May 20 01:00:00 AM EDT 2020

This applies to calendar.getTime() in the setStartTime method. The first method is only included because the day selected is linked as a global variable. Problem lies within the second method.

I could probably manually change the final date as string and convert it back but I don't want to add unnecessary complexity. I want to find out why Calendar won't succeed since this should be extremely basic.

In the end my goal is to add the date to firebase. It must be uniform across Android, IOS and Web. Therefore I need the date to be in this exact format.

Here is code:

    SimpleDateFormat serviceSETimeFormat = new SimpleDateFormat("MMM dd, yyyy HH:mm aa");

public void setStartDay(View view)
{
    Calendar calendar = Calendar.getInstance();
    DatePickerDialog picker = new DatePickerDialog(ServiceCreation.this,
        new DatePickerDialog.OnDateSetListener() {
            @Override
            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                GregorianCalendar mCal = new GregorianCalendar(year, monthOfYear, dayOfMonth);
                Date dateForDisplay = mCal.getTime();
                int startHourIndex = getThirdSpaceIndex(dateForDisplay.toString());
                //SimpleDateFormat fmt = new SimpleDateFormat("dd-MMM-yyyy");
                serviceSETimeFormat.setCalendar(mCal);
                Date dateFormatted = null;
                try{
                    dateFormatted= serviceSETimeFormat.parse(serviceSETimeFormat.format(mCal.getTime()));
                    currBaseStartDate=dateFormatted;
                }
                catch(ParseException p){
                    Log.wtf("SC","Parse exc34");
                }
                Log.wtf("SC","Here is orig date: "+dateFormatted);
                //Don't show hh/mm/ss (these are held as 0)
                startDayView.setText(dateForDisplay.toString().substring(0,startHourIndex));
                startEndTimeWrapper.setVisibility(View.VISIBLE);
            }
        }, calendar.get(Calendar.YEAR),calendar.get(Calendar.MONTH),calendar.get(Calendar.DAY_OF_MONTH));
    long currentTime = new Date().getTime();
    picker.getDatePicker().setMinDate(currentTime);
    picker.show();
}


public void setStartTime(View view) {
    TimePickerDialog mTimePicker = new TimePickerDialog(ServiceCreation.this,
        android.R.style.Theme_Holo_Light_Dialog_NoActionBar, new TimePickerDialog.OnTimeSetListener() {
        @Override
        public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
            String minuteAsS =  Integer.toString(selectedMinute);
            if(minuteAsS.length()==1){
                minuteAsS="0"+minuteAsS;
            }
            if(selectedHour==0){
                startTimeView.setText((selectedHour+12)+":"+minuteAsS+" AM");
            }
            else if(selectedHour<12){
                startTimeView.setText(selectedHour+":"+minuteAsS+" AM");
            }
            else if(selectedHour==12){
                startTimeView.setText((selectedHour)+":"+minuteAsS+" PM");
            }
            else{
                startTimeView.setText((selectedHour-12)+":"+minuteAsS+" PM");
            }

            (findViewById(R.id.endTimeWrapper)).setVisibility(View.VISIBLE);
            Calendar calendar = new GregorianCalendar();
            calendar.setTime(currBaseStartDate);
            calendar.set(Calendar.MINUTE, selectedMinute);
            Log.wtf("SC","Hour before condition: "+selectedHour);
            if(selectedHour>=12){
                if(selectedHour!=12){
                    Log.wtf("SC","Decrementing hour");
                    selectedHour-=12;
                }
                Log.wtf("SC","Hour set: "+selectedHour);
                calendar.set(Calendar.AM_PM,Calendar.PM);
                calendar.set(Calendar.HOUR_OF_DAY,selectedHour);
            }
            else{
                calendar.set(Calendar.AM_PM,Calendar.AM);
                calendar.set(Calendar.HOUR_OF_DAY,selectedHour);
            }
            startTime=calendar.getTime();
            Log.wtf("SC","Here is orig start time: "+startTime);
            try{
                startTime=serviceSETimeFormat.parse(serviceSETimeFormat.format(startTime));
                Log.wtf("SC","Here is parsed start time: "+startTime);
            }
            catch(ParseException e){
                Log.wtf("SC","START DATE PARSE EXCEPTION");
            }
        }
    }, 12, 0, false);
    mTimePicker.setTitle("Select Start Time: ");
    mTimePicker.show();
}

Solution

  • Three points:

    1. Even on Android and even on API levels under 26 consider using java.time, the modern Java date and time API. Calendar, GregorianCalendar, SimpleDateFormat and Date are confusing, poorly designed and long outdated classes. The modern API is so much nicer to work with.
    2. You don’t want a Calendar or Date with AM or PM. You want to keep your model and your presentation to the user separate. In the model you want a LocalDateTime or other appropriate date-time object, and you don’t want to worry about its format, whether it uses a 12 hours or 24 hours clock. For display to your user you want date and time in a string in an appropriate format for you user, including AM or PM. This is also why your views have a setText method and no setDate method.
    3. No Date with AM/PM format can exist. You are asking the impossible.

    java.time and ThreeTenABP

    Construct a LocalDate object from your three integers for year, month and day:

        int year = 2020;
        int monthOfYear = Calendar.MAY; // Don’t use Calendar, though
        int dayOfMonth = 11;
    
        LocalDate date = LocalDate.of(year, monthOfYear + 1, dayOfMonth);
        System.out.println(date);
    

    Output:

    2020-05-11

    I believe that your date picker numbers months from 0 for January through 11 for December. So we need to add 1 to the month number to convert to the way humans and LocalDate number months. I am only using the constant from the Calendar class to initialize the variable to a 0-based month number, don’t do it in your code since the value comes from the data picker. Calendar uses the same insane numbering.

    To display the date back to the user format it into a string using a formatter:

        DateTimeFormatter dateFormatter = DateTimeFormatter
                .ofLocalizedDate(FormatStyle.MEDIUM)
                .withLocale(Locale.US);
        String dateForDisplay = date.format(dateFormatter);
        System.out.println(dateForDisplay);
    

    May 11, 2020

    Give the appropriate locale, or leave out the call to withLocale() to rely on the locale setting of the device.

    When the user selects the time:

        int selectedHour = 1;
        int selectedMinute = 0;
    
        LocalTime selectedTime = LocalTime.of(selectedHour, selectedMinute);
        LocalDateTime dateTime = date.atTime(selectedTime);
        System.out.println(dateTime);
    

    2020-05-11T01:00

    The time can be formatted in a way that is very similar to what we did with the date:

        DateTimeFormatter timeFormatter = DateTimeFormatter
                .ofLocalizedTime(FormatStyle.MEDIUM)
                .withLocale(Locale.US);
        String timeForDisplay = selectedTime.format(timeFormatter);
        System.out.println(timeForDisplay);
    

    1:00:00 AM

    Question: Doesn’t java.time require Android API level 26?

    java.time works nicely on both older and newer Android devices. It just requires at least Java 6.

    • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
    • In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
    • On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.

    Links