Difference between Dates - Kotlin - kotlin

Guys!
I need some help here. It's basically a logical question.
I have a Date of publication of an item.
Example: 10/12/2022
I need a function to calculate this date from now, with a few rules:
If the difference is less than an hour, returns string: "Published 15 minutes ago"
If the difference is less than a day, returns string: "Published 4 hours ago"
If the difference is greater than a day, returns string: "Published in 10/12/2022"
How can I do this?
Thanks a lot for you help!
private fun calculateTime(date: Long): String {
return ""
}

Assuming you don't actually use long as parameter but a temporal unit, the solution could look like this
fun calculateTime(date: Instant): String {
val passedTime = Duration.between(date, Instant.now())
return if (passedTime.toHours() < 1)
"Published 15 minutes ago"
else if (passedTime.toDays() < 1)
"Published 4 hours ago"
else {
DateTimeFormatter.ofPattern("dd/MM/yyyy")
.withZone(ZoneId.systemDefault())
.format(date)
}
}

Related

How to get the difference betweeen two dates (jetpack compose/kotlin)?

I have to calculate how many days are left between the date selected by the user through a DatePicker and the current date
I was trying to write something like this:
val simpleDate = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
val date = simpleDate.parse(event!!.date!!)
val diff = Duration.between(LocalDate.now(), date.toInstant())
val leftDays = diff.toDays()
Your mix of outdated (SimpleDateFormat, 'Date') and modern (LocalDate) APIs is not optimal, I think:
I would use plain java.time here, becauseā€¦
you can obviously use it in your application
it has a specific class for datetime Strings of the pattern you have shown in your question: an OffsetDateTime and
there's a java.time.Duration which you have tried to use
Here's an example:
fun main(args: Array<String>) {
// example input, some future datetime
val input = "2022-12-24T13:22:51.837Z"
// parse that future datetime
val offsetDateTime = OffsetDateTime.parse(input)
// build up a duration between the input and now, use the same class
val duration = Duration.between(OffsetDateTime.now(), offsetDateTime)
// get the difference in full days
val days = duration.toDays()
// print the result as "days left"
println("$days days left")
}
Output:
110 days left
If you don't receive a datetime but a date without time (just day of month, month of year and year), then use a LocalDate and calculate the ChronoUnit.DAYS.between(today, futureDate)
fun main(args: Array<String>) {
// example input, some future date
val input = "2022-12-24"
// parse that
val futureDate = LocalDate.parse(input)
// get the difference in full days
val days = ChronoUnit.DAYS.between(LocalDate.now(), futureDate)
// print the result
println("$days days left")
}
Output (again):
110 days left
Try below code -
val previousTimeDouble: Double = previousTime.toDouble()
val nowTimeDouble: Double = System.currentTimeMillis().toDouble()
val dateNowString: String = dateNow.toString();
val time: Long = (nowTimeDouble - previousTimeDouble).toLong()
val difference: String= differenceBetweenTwoTimes(time)
Log.d("difference", difference)
Function for converting time difference into units -
fun differenceBetweenTwoTimes(time: Long): String {
var x: Long = time / 1000
var seconds = x % 60
x /= 60
var minutes = x % 60
x /= 60
var hours = (x % 24).toInt()
x /= 24
var days = x
return String.format("%02d:%02d:%02d", hours, minutes, seconds)
}

How to convert milliseconds to Stopwatch format in Kotlin? (HH:MM:SS)

I have time in milliseconds which i'm getting by:
val past = System.currentTimeMillis()
val future = System.currentTimeMillis() + 1000L
// getting duration every second. imagine working stopwatch here
val duration = future - past
// Imconvert duration to HH:MM:SS
. I need to convert it to stopwatch format (HH:MM:SS). I know there is a lot of options. But what is the most modern and easiest way to do it?
Be careful how you get milliseconds in the first place
First and foremost, you should not use System.currentTimeMillis() for elapsed time. This clock is meant for wallclock time and is subject to drifting or leap second adjustments that can mess up your measurements significantly.
A better clock to use would be System.nanoTime(). But in Kotlin you don't need to call it explicitly if you want to measure elapsed time. You can use nice utilities like measureNanoTime, or the experimental measureTime which directly returns a Duration that you can format:
val durationNanos = measureNanoTime {
// run the code to measure
}
val duration = measureTime {
// run the code to measure
}
Convert milliseconds to Duration
If you don't want to use measureTime and still have just a number of milliseconds or nanoseconds, you can convert them to a Duration by using one of the extension properties of Duration.Companion:
import kotlin.time.Duration.Companion.milliseconds
val durationMillis: Long = 1000L // got from somewhere
val duration: Duration = durationMillis.milliseconds
However, that is quite awkward and that's the reason why those extensions were deprecated for a while. They were restored because they are nice to use with number literals, but they are not so nice with variable names. Instead, you can use Long.toDuration():
import kotlin.time.*
val durationMillis = 1000L // got from somewhere
val duration = durationMillis.toDuration(DurationUnit.MILLISECONDS)
Format Duration
If you just want a nice visual format, note that the kotlin.time.Duration type is already printed nicely thanks to its nice toString implementation:
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.milliseconds
fun main() {
val duration = 4.minutes + 67.seconds + 230.milliseconds
println(duration) // prints 5m 7.23s
}
See it in the playground: https://pl.kotl.in/YUT6FZA0l
If you really want the format you're asking for, you may also use toComponents as #Can_of_awe mentioned:
// example duration, built from extensions on number literals
val duration = 4.minutes + 67.seconds + 230.milliseconds
val durationString = duration.toComponents { hours, minutes, seconds, _ ->
"%02d:%02d:%02d".format(hours, minutes, seconds)
}
println(durationString) // prints 00:05:07
A more Kotlin-style straightforward way of doing this:
val durationString = duration.milliseconds.toComponents { hours, minutes, seconds, _ ->
"%02d:%02d:%02d".format(hours, minutes, seconds)
}
Where the .milliseconds extension is from import kotlin.time.Duration.Companion.milliseconds

For loop must have an iterator()

I need this service in which if the person stays for longer than 30 minutes, they have to pay an extra $10 every 15 minutes (and for the fraction of the 15 as well).
I designed it like this so far:
var checkInTime: Calendar
val totalTime: Long
get() = (Calendar.getInstance().timeInMillis - checkInTime.timeInMillis) / MIN_IN_MILISEC
fun getTime(totalTime:Long): Int{
var finalPrice = 0
var initialPrice = 20
if(totalTime<31){
finalFee = initialPrice
} else {
val extraPrice = 10
val extraTime = 15
finalFee = initialPrice
for(extraTime in totalTime){
finalFee += extraTime
}
return finalFee
}
I get the error "For loop must have an iterator()" when I try to loop through the totalTime when it's more than 30 minutes so that I can add $10 every 15 extra minutes. I need some help as to how to add to the finalFee every extra 15 minutes the person stays since my method is not working.
Thank you.
Let's take a look at your getTime function:
You're using a Long as totalTime. You can measure it in minutes to simplify your calculation (since all time values are measured in minutes). Since a Long type in Kotlin stores a integer up to 9,223,372,036,854,775,807 and no soul on Earth will use your service for that long (this represents 17 billion milleniums), you can just use an Int.
You're not declaring the finalFee variable, thus the code will raise an
"Unresolved reference" error. Since you're not using the finalPrice variable, I'm assuming you wanted to use this instead.
You're trying to iterate over a numeric value (in this case, totalTime, which is a Long). You can iterate over each element of a List, but how would you iterate over each element of an integer? I'm assuming you want to do a certain action totalTime number of times. In this case, you would use ranges.
You're also not using the variables extraPrice and extraTime.
There's code that's common to both if-else conditions (finalPrice = initialPrice), so you can extract that to outside the if-statement.
Refactoring your function:
fun getTime(totalTime: Int): Int {
var finalPrice = 20
if (totalTime >= 30) {
(0 until totalTime).forEach {
finalPrice += 15
}
}
return finalPrice
}
It's shorter, but still doesn't do what it's supposed to: let's suppose totalTime is equal to 45. The person got 30 minutes costing $20 and only have to pay $10 for every 15 minutes, therefore will only pay $30 total. Your function is considering that the person will have to pay $15 for every minute they stayed, because it uses a for-loop that goes from 0 to totalTime. For that, you need a for-loop that goes from 30 (the time limit) from the total time (the totalTime) every 15 minutes:
fun getTime(totalTime: Int): Int {
var finalPrice = 20
if (totalTime > 30) {
(30 until totalTime step 15).forEach {
finalPrice += 10
}
}
return finalPrice
}
Better yet, you don't even need a for-loop, you can just use maths:
fun getTime(totalTime: Int): Int {
var finalPrice = 20
if (totalTime > 30) {
finalPrice += ((totalTime - 30) / 15) * 10
// ^^^^^^^^^^^^^^^^ Get the exceeding time
// ^^^^^^^^^^^^^^^^^^^^^^^ How many 15 minutes are there?
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Put $10 for every 15 minutes
}
return finalPrice
}
The last part: your question said you need to consider the fraction of 15 as well. Therefore, you need to use a real value, not an integer. Let's change it to a Double:
fun getTime(totalTime: Int): Double {
var finalPrice = 20.0
if (totalTime > 30) {
finalPrice += ((totalTime - 30) / 15.0) * 10
}
return finalPrice
}
Let's test your function:
fun main() {
println(getTime(0)) // Outputs 20.0
println(getTime(10)) // Outputs 20.0
println(getTime(30)) // Outputs 20.0
println(getTime(45)) // Outputs 30.0
println(getTime(60)) // Outputs 40.0
println(getTime(70)) // Outputs 46.666...
}

In ANTLR how should I implement a Visitor with state / context?

I'm trying to write a simple ANTLR parser to handle date adjustment, so for example I could write: +1w+1d to mean "traverse a week and a day", or MIN(+30d, +1m) to mean "30 days from the input date, or 1 month, whichever is sooner". These rules should be composable, so MIN(+30d, +1m)+1d means "the day after (30 days from the input date, or 1 month from the input date, whichever is sooner)" and +1dMIN(+30d, +1m) means "30 days from (the day after the input date) or 1 month after (the day after the input date) whichever is sooner".
[I appreciate the examples here are relatively trite - the real grammar needs to understand about weekends, holidays, month boundaries etc, so it might be things like "one month after(the end of the input date's month or the Friday after the input date - whichever comes first)" etc etc]
The code I want to write is:
DateAdjutmeParser parser = buildFromString("MAX(+30d,+1m)");
ParseTree tree = parser.rootNode();
return new MyVisitor().visit(tree, LocalDate.of(2020,4,23)); //Not allowed extra parameters here.
The problem is how exactly can I pass the "context Date"? I can't store it in the MyVisitor class as a member since the visit() call is recursive and that would overwrite the context. I could build up a parallel set of objects that did have the right methods, but that seems a lot of boilerplate.
Is there an ANTLR solution?
More details:
This is the Visitor I'd like to write:
public class MyVisitor extends DateAdjustBaseVisitor<LocalDate> {
#Override
public LocalDate visitOffsetRule(DateAdjustParser.OffsetRuleContext ctx) {
LocalDate contextDate = ???; //
return contextDate.plus(Integer.valueOf(ctx.num.toString()), ChronoUnit.valueOf(ctx.unit.toString()));
}
#Override
public LocalDate visitMinMaxRule(DateAdjustParser.MinMaxRuleContext ctx) {
LocalDate contextDate = ???; //
LocalDate left = this.visitChildren(ctx.left, contextDate);
LocalDate right = this.visitChildren(ctx.right, contextDate);
if(ctx.type.getText().equals("MIN")) {
return left.compareTo(right) > 0 ? left : right;
} else {
return left.compareTo(right) < 0 ? left : right;
}
}
}
here's my grammar:
grammar DateAdjust;
rootNode: offset+;
offset
: num=NUMBER unit=UNIT #OffsetRule
| type=( MIN | MAX ) '(' left=offset ',' right=offset ')' #MinMaxRule
;
UNIT: [dwmy]; //Days Weeks Months Years
NUMBER: [+-]?[0..9]+;
MAX: 'MAX';
MIN: 'MIN';
Not an Antlr-specific solution, but a typical DSL solution is to use a scoped state table (aka symbol table) to accumulate the results of an AST/CST walk.
See this answer for an implementation. Another exists in the Antlr repository.

Period of weeks to string with weeks instead of days only

var period = Period.ofWeeks(2)
println("period of two weeks: $period")
gives
period of two weeks: P14D
Unfortunately for my purpose I need P2W as output, so directly the weeks instead of weeks converted to days. Is there any elegant way to do this, besides building my Period string manually?
Your observation is true. java.time.Period does not really conserve the week value but automatically converts it to days - already in the factory method during construction. Reason is that a Period only has days and months/years as inner state.
Possible workarounds or alternatives in the order of increasing complexity and count of features:
You write your own class implementing the interface java.time.temporal.TemporalAmount with weeks as inner state.
You use the small library threeten-extra which offers the class Weeks. But be aware of the odd style of printing negative durations like P-2W instead of -P2W. Example:
Weeks w1 = Weeks.of(2);
Weeks w2 = Weeks.parse("P2W");
System.out.println(w1.toString()); // P2W
System.out.println(w2.toString()); // P2W
Or you use my library Time4J which offers the class net.time4j.Duration. This class does not implement the interface TemporalAmount but offers a conversion method named toTemporalAmount(). Various normalization features and formatting (ISO, via patterns and via net.time4j.PrettyTime) and parsing (ISO and via patterns) capabilities are offered, too. Example:
Duration<CalendarUnit> d1 = Duration.of(2, CalendarUnit.WEEKS);
Duration<IsoDateUnit> d2 = Duration.parseWeekBasedPeriod("P2W"); // also for week-based-years
System.out.println(d1.toString()); // P2W
System.out.println(PrettyTime.of(Locale.US).print(d2); // 2 weeks
As extra, the same library also offers the class net.time4j.range.Weeks as simplified week-only duation.
The Period toString method only handles Day, Month and Year.
as you can see below is toString() method from class java.time.Period.
So Unfortunately I think you need to create it yourself.
/**
* Outputs this period as a {#code String}, such as {#code P6Y3M1D}.
* <p>
* The output will be in the ISO-8601 period format.
* A zero period will be represented as zero days, 'P0D'.
*
* #return a string representation of this period, not null
*/
#Override
public String toString() {
if (this == ZERO) {
return "P0D";
} else {
StringBuilder buf = new StringBuilder();
buf.append('P');
if (years != 0) {
buf.append(years).append('Y');
}
if (months != 0) {
buf.append(months).append('M');
}
if (days != 0) {
buf.append(days).append('D');
}
return buf.toString();
}
}