1

At the moment I got a class 'Flight' with Date datatypes; departure and arrival datetime.

The adding of flights happens by user input. The day should be the current date automatically and the time is the user's choice. Which means a person only has to input HH:mm.

At the moment it is a bit confusing with all the choices; Timestamp, Date, Localtime etc.

How can I make a simple user input with a scanner for this problem? It should take todays date, add the user input containing the time and add it together to fit into my Date datatype.

Anyone has a clue how to do this or could provide some tips / best practices?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
MrEmper
  • 225
  • 1
  • 4
  • 18
  • 2
    A ggod source is [Oracle Tutorial: Date Time](https://docs.oracle.com/javase/tutorial/datetime/). I recommend you ignore the many outdated websites that teach the also outdated classes like `TimeStamp` and `Date`. I certainly understand that you get easlily confused. – Ole V.V. May 06 '18 at 11:47
  • Are you developing a console app? – Mạnh Quyết Nguyễn May 06 '18 at 11:47
  • Yes a console app – MrEmper May 06 '18 at 11:47
  • 1
    `LocalDate.now(ZoneId.of("Europe/Copenhagen").atTime(LocalTime.parse("17:45").atZone(ZoneId.of("Europe/Copenhagen"))`. Of course declare your user’s desired time zone a constant. – Ole V.V. May 06 '18 at 12:34

2 Answers2

1

Since you said that you are developing a desktop app and you need the current date (on the pc) you can use a combination of LocalDate and LocalTime to achieve your goal.

Here is the code:

public static void main(String[] args) {
LocalDate currentDate = LocalDate.now();
LocalTime userInputTime = null;

Scanner sc = new Scanner(System.in);
String dateTimeLine = sc.nextLine();
sc.close();

DateTimeFormatter dt = DateTimeFormatter.ofPattern("HH:mm");
userInputTime = LocalTime.parse(dateTimeLine,dtf);

System.err.println(LocalDateTime.of(currentDate, userInputTime));
}

First, you use LocalDate.now() in order to generate the current date (only the date, without hours, minutes and seconds).

Next we use Scanner in order to read a string entered by the user. In order to convert the string to a LocalTime (this class contains info only about time in a day, so it has values for hours,minutes,seconds and nanoseconds), we have to define a DateTimeFormatter. The DateTimeFormatter defines how the string will be converted into LocalTime instance.

In the code I just wrote, I said that the string input will be of type "hours:minutes". For example, possible values are:

"10:25" - 10 hours and 25 minutes,

"23:00" - 23 hours and 0 minutes,

"02:13" - 2 hours and 13 minutes.

After we create the LocalTime object, all we have to do is to join the date and time objects in order to create a LocalDateTime object which is done in this line:

LocalDateTime.of(currentDate, userInputTime)

So lets say that the date on your current PC is 2018-05-06. If you run the program and enter 10:50 in the console, the output should be a LocalDateTime object that has 2018-05-06 as a date and 10 hours and 50 minutes as time of the day.

It is important to note that this line:

userInputTime = LocalTime.parse(dateTimeLine,dtf);

will throw an java.time.format.DateTimeParseException if the entered string by the user does not satisfy the required format.

Dimitar Spasovski
  • 2,023
  • 9
  • 29
  • 45
  • 1
    Fine answer, thanks. I recommend you pass a time zone to `LocalDate.now(ZoneId)` so that both you and the next programmer reading your code knows that you have made a conscious choice (even if you specify `ZoneId.systemDefault()`). – Ole V.V. May 06 '18 at 13:53
  • I specifically chose not to add a zone because the OP said that this will be a Desktop application. Now imagine if he sets `Europe/London` as a time zone and he sends the app to a user in Australia.If the Australian user tests the app at 4 AM (for example), he will be presented with a wrong date value because of the time zone. I could add the `ZoneId.systemDefault()` line but I believe that will only confuse the user because he is clearly a programming beginner and adding a line that does nothing (except making the intentions of the developer clear) won't help him much. – Dimitar Spasovski May 06 '18 at 14:09
  • 1
    @Dvorog Passing `ZoneId.systemDefault()` is not “doing nothing”. It demonstrates that the author of that line understands that a time zone is in play when determining the current date. This is a crucial concept that all too few programmers understand. All the more important to stress this point with a beginner programmer. – Basil Bourque May 06 '18 at 15:50
  • I would say that a beginner programmer probably has no idea what time zones in programming are or how they work and I do believe that he firsts needs to learn how to manipulate with simple date objects before moving up to zones and time representation in general. I do agree with everything that you stated in your last comment but I think that it is not applicable to beginners. – Dimitar Spasovski May 06 '18 at 15:57
1

tl;dr

LocalDateTime.of(                 // A `LocalDateTime` represents a set of *potential* moments along a range of about 26-27 hours. Not an actual moment, not a point on the timeline.
    LocalDate.systemDefault() ,   // Get the JVM’s current default time zone. Can change at any moment *during* runtime. When crucial, always confirm with the user.
    LocalTime.parse( "14:57" )    // Parse a string in standard ISO 8601 format as a time-of-day without regard for time zone or offset-from-UTC.
)                                 // Returns a `LocalDateTime` object.
.atZone(                          // Determine an actual moment, a point on the timeline.
    ZoneId( "Africa/Tunis" )      // Specify a time zone as `Continent/Region`, never as 3-4 letter pseudo-zones such as `PST`, `CST`, or `IST`.
)                                 // Returns a `ZonedDateTime` object.
.toInstant()                      // Extracts a `Instant` object from the `ZonedDateTime` object, always in UTC by default.

Details

At the moment it is a bit confusing

Date-time handling is very confusing work.

Tips:

  • Forget about your own time zone. Think in terms of UTC rather than your own parochial time zone.
  • Learn the difference between real moments (points on the timeline), and date-time approximations that are not on the timeline, often-called “local” values.
  • Be careful when reading Stack Overflow or other places on the internet about date-time. You will encounter poor advice and many wrong solutions.

with all the choices; Timestamp, Date, Localtime etc.

Never use the troublesome old legacy date-time classes bundled with the earliest versions of Java. Never use java.sql.Timestamp, java.util.Date, java.util.Calendar, and so on.

➡ Use only classes in the java.time package.

The java.time classes are an industry-leading date-time framework. Extremely well-designed and thought-through, with lessons learned from the Joda-Time project it succeeds.

Anyone has a clue how to do this or could provide some tips / best practices?

You might be sorry you asked. Read on.

At the moment I got a class 'Flight' with Date datatypes; departure and arrival datetime.

So define a Flight class.

In real-life, flights happen far enough out in the future that we risk politicians changing the definition of the time zone. Most commonly these changes are adopting/dropping/altering Daylight Saving Time (DST). But arbitrary changes are made periodically for all kinds of reasons. We could debate the wisdom/sanity of such changes, but the fact is they happen. They happen quite frequently as politicians seemly oddly prone to making these changes around the world in many countries. And nearly all of them do so with little forewarning, sometimes just weeks. Or even with no warning at all, as North Korea did this week.

I have no understanding of how airlines actually work, but from poking around airline schedules and various readings, it seems they try to maintain their schedules using the zoned time of the departing locality. So if a flight is scheduled to depart LAX at 6 AM, they keep that flight schedule on the day before, the day of, and the day after a DST change-over. If this is indeed the general intent, that means sitting-around killing time on one DST cut-over while trying to save an hour on the opposite DST cut-over. Apparently, Amtrak adopts this practice for its trains. Let’s proceed with this approach.

Using this “imaginary” schedule approach means we cannot determine for certain the exact moment when 6 AM will occur in the future. So we need to record our desire for that date and that time-of-day without applying a time zone. But we must record the desired time zone so we know in what context we can later determine the exact moment, when close enough in time that we needn’t worry about zone changes.

So we use LocalDate and LocalTime types, as they purposely lack any concept of time zone (a name in Continent/Region format) or offset-from-UTC (a number of hours-minutes-seconds).

The ZoneId class represents a time zone.

I am using the word Unzoned in the names to remind us that these values do not represent actual moments on the timeline. The word “local” tends to confuse beginners.

public class Flight {
    private String flightNumber;
    private LocalDate departureDateUnzoned;
    private LocalTime departureTimeUnzoned;
    private ZoneId departureZoneId ;
}

As for arrival, store the span-of-time expected for that flight rather than the arrival date-time. You can calculate the arrival, so no need to store it. The Duration class tracks a number of hours, minutes, seconds, and fractional second.

To calculate the arrival, let’s return a single value using the LocalDateTime class, which simply combines a LocalDate with a LocalTime. We could have used this type to make a single departureUnzoned member variable in our class definition. I went with separate LocalDate and LocalTime as building blocks so you would understand the pieces. So many programmers use their intuition rather than the documentation to assume that LocalDateTime means a specific moment in a locality when actually it means just the opposite. (You will find many incorrect Answers on Stack Overflow advising LocalDateTime when actually Instant or ZonedDateTime should be used.)

Let's add a method to calculate that arrival.

public class Flight {
    private String flightNumber;
    private LocalDate departureDateUnzoned;
    private LocalTime departureTimeUnzoned;
    private ZoneId departureZoneId;
    private Duration duration;

    public LocalDateTime arrivalDateTimeUnzoned () {
        LocalDateTime departureUnzoned = LocalDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned );
        LocalDateTime ldt = departureUnzoned.plus( this.duration );
        return ldt;
    }
}

But this returned LocalDateTime fails to account for time zone. Usually, airlines and train report to customers the expected arrival time adjusted into the time zone of that region. So we need an arrival time zone. And we can use that zone when calculating the arrival, thereby producing a ZonedDateTime. A ZonedDateTime is a specific moment, it is a point on the timeline, unlike LocalDateTime. But remember, if we are scheduling flights out into the future, the calculated ZonedDateTime will change if our code is run after politicians redefine the time zone.

public class Flight {
    private String flightNumber;
    private LocalDate departureDateUnzoned;
    private LocalTime departureTimeUnzoned;
    private ZoneId departureZoneId;
    private Duration duration;
    private ZoneId arrivalZoneId;

    public ZonedDateTime arrivalDateTimeZoned () {
        ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
        ZonedDateTime zdt = departureZoned.plus( this.duration );
        return zdt;
    }
}

Back to the part of your Question about determining the date automatically. That requires a time zone. For any given moment, the date varies around the globe. Think about that. A few minutes after midnight in Paris France is a new day, while still “yesterday” in Montréal Québec.

We can ask for the JVM’s current default time zone.

ZoneId userZoneId = ZoneId.systemDefault() ;

But when crucial, you must confirm with the user.

ZoneId userZoneId = ZoneId.of( "America/Montreal" ) ; 

So now we can add the constructor you asked for, passing the time-of-day (a LocalTime, and guessing the time zone by using the JVM’s current default.

But we still need all the other pieces. So defaulting the date does not save us much.

public class Flight {
    private String flightNumber;
    private LocalDate departureDateUnzoned;
    private LocalTime departureTimeUnzoned;
    private ZoneId departureZoneId;
    private Duration duration;
    private ZoneId arrivalZoneId;

    // Constructor
    public Flight ( String flightNumber , LocalTime departureTimeUnzoned , ZoneId departureZoneId , Duration duration , ZoneId arrivalZoneId ) {
        this.flightNumber = flightNumber;
        this.departureTimeUnzoned = departureTimeUnzoned;
        this.departureZoneId = departureZoneId;
        this.duration = duration;
        this.arrivalZoneId = arrivalZoneId;

        // Determine today’s date using JVM’s current default time zone. Not advisable in many business scenarios, but specified by our Question at hand.
        ZoneId z = ZoneId.systemDefault();
        LocalDate today = LocalDate.now( z );
        this.departureDateUnzoned = today;
    }

    public ZonedDateTime arrivalDateTimeZoned () {
        ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
        ZonedDateTime zdt = departureZoned.plus( this.duration );
        return zdt;
    }
}

Let’s add a toString method for reporting.

We represent the date-time values as strings in standard ISO 8601 formats. The java.time classes use these standard formats when parsing/generating strings. The Z on the end is pronounced Zulu and means UTC.

While airlines and trains report date-times to their customers in the regions’ time zones, we can assume they use only UTC internally. The Instant class represents values in UTC specifically. So our toString extracts Instant objects from the ZonedDateTime objects.

And we add a main method for demonstration. Here is the complete class, with import etc.

package com.basilbourque.example;

import java.time.*;

public class Flight {
    private String flightNumber;
    private LocalDate departureDateUnzoned;
    private LocalTime departureTimeUnzoned;
    private ZoneId departureZoneId;
    private Duration duration;
    private ZoneId arrivalZoneId;

    // Constructor
    public Flight ( String flightNumber , LocalTime departureTimeUnzoned , ZoneId departureZoneId , Duration duration , ZoneId arrivalZoneId ) {
        this.flightNumber = flightNumber;
        this.departureTimeUnzoned = departureTimeUnzoned;
        this.departureZoneId = departureZoneId;
        this.duration = duration;
        this.arrivalZoneId = arrivalZoneId;

        // Determine today’s date using JVM’s current default time zone. Not advisable in many business scenarios, but specified by our Question at hand.
        ZoneId z = ZoneId.systemDefault();
        LocalDate today = LocalDate.now( z );
        this.departureDateUnzoned = today;
    }

    public ZonedDateTime arrivalDateTimeZoned () {
        ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
        ZonedDateTime zdt = departureZoned.plus( this.duration );
        return zdt;
    }

    @Override
    public String toString () {
        ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
        String flightInUtc = departureZoned.toInstant().toString() + "/" + this.arrivalDateTimeZoned().toInstant().toString();
        return "Flight{ " +
                "flightNumber='" + this.flightNumber + '\'' +
                " | departureDateUnzoned=" + this.departureDateUnzoned +
                " | departureTimeUnzoned=" + this.departureTimeUnzoned +
                " | departureZoneId=" + this.departureZoneId +
                " | departureZoned=" + departureZoned +
                " | duration=" + this.duration +
                " | arrivalZoneId=" + this.arrivalZoneId +
                " | calculatedArrival=" + this.arrivalDateTimeZoned() +
                " | flightInUtc=" + flightInUtc +
                " }";
    }

    public static void main ( String[] args ) {
        LocalTime lt = LocalTime.of( 6 , 0 ); // 6 AM.
        Flight f = new Flight( "A472" , lt , ZoneId.of( "America/Los_Angeles" ) , Duration.parse( "PT6H37M" ) , ZoneId.of( "America/Montreal" ) );
        String output = f.toString();
        System.out.println( output );
    }
}

When run.

Flight{ flightNumber='A472' | departureDateUnzoned=2018-05-06 | departureTimeUnzoned=06:00 | departureZoneId=America/Los_Angeles | departureZoned=2018-05-06T06:00-07:00[America/Los_Angeles] | duration=PT6H37M | arrivalZoneId=America/Montreal | calculatedArrival=2018-05-06T12:37-07:00[America/Los_Angeles] | flightInUtc=2018-05-06T13:00:00Z/2018-05-06T19:37:00Z }

To use this from the console, ask the user for the time-of-day in 24-hour clock. Parse the input string.

String input = "14:56" ;  // 24-hour clock.
LocalTime lt = LocalTime.parse( input ) ;

This is far from complete for real-world work. But hopefully it makes for an educational example.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154