Comment obtenir l'heure de début et l'heure de fin d'une journée?

121

Comment obtenir l'heure de début et l'heure de fin d'une journée?

un code comme celui-ci n'est pas précis:

 private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
}

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
}

Ce n'est pas précis à la milliseconde.

kalman03
la source
8
Que voulez-vous dire par «inexact»?
Kirk Woll
Quel est le problème avec le code? Qu'obtenez-vous auquel vous ne vous attendez pas? Selon quel fuseau horaire voulez-vous que vos journées commencent et se terminent?
Mark Reed
4
Le code ci-dessus n'utilise même pas la date passée dans la méthode. Il devrait faire: calendar.setTime (date) après la création du calendrier.
Jerry Destremps
2
Cette question suppose que les valeurs date-heure ont une résolution de millisecondes. Vrai pour java.util.Date et Joda-Time. Mais False pour le package java.time en Java 8 (nanosecondes), certaines bases de données telles que Postgres (microsecondes), les bibliothèques unix (secondes entières), et peut-être d'autres. Voir ma réponse pour une approche plus sûre en utilisant Half-Open.
Basil Bourque
2
En fait, vous devriez obtenir le début du jour suivant et lors de l'itération des éléments ce jour-là (en supposant que vous vouliez le faire), vous spécifierez la limite supérieure en utilisant <au lieu de <=. Cela exclurait le jour suivant, mais inclurait toute la journée précédente jusqu'à la nanoseconde.
Daniel F

Réponses:

115

tl; dr

LocalDate                       // Represents an entire day, without time-of-day and without time zone.
.now(                           // Capture the current date.
    ZoneId.of( "Asia/Tokyo" )   // Returns a `ZoneId` object.
)                               // Returns a `LocalDate` object.
.atStartOfDay(                  // Determines the first moment of the day as seen on that date in that time zone. Not all days start at 00:00!
    ZoneId.of( "Asia/Tokyo" ) 
)                               // Returns a `ZonedDateTime` object.

Début de la journée

Obtenez la longueur totale de la journée d'aujourd'hui dans un fuseau horaire.

En utilisant l'approche semi-ouverte, où le début est inclusif tandis que la fin est exclusive . Cette approche résout la faille dans votre code qui ne tient pas compte de la toute dernière seconde de la journée.

ZoneId zoneId = ZoneId.of( "Africa/Tunis" ) ;
LocalDate today = LocalDate.now( zoneId  ) ;

ZonedDateTime zdtStart = today.atStartOfDay( zoneId ) ;
ZonedDateTime zdtStop = today.plusDays( 1 ).atStartOfDay( zoneId ) ;

zdtStart.toString () = 2020-01-30T00: 00 + 01: 00 [Afrique / Tunis]

zdtStop.toString () = 2020-01-31T00: 00 + 01: 00 [Afrique / Tunis]

Voir les mêmes moments en UTC.

Instant start = zdtStart.toInstant() ;
Instant stop = zdtStop.toInstant() ;

start.toString () = 2020-01-29T23: 00: 00Z

stop.toString () = 2020-01-30T23: 00: 00Z

Si vous voulez que la journée entière d'une date soit vue en UTC plutôt que dans un fuseau horaire, utilisez OffsetDateTime.

LocalDate today = LocalDate.now( ZoneOffset.UTC  ) ;

OffsetDateTime odtStart = today.atTime( OffsetTime.MIN ) ;
OffsetDateTime odtStop = today.plusDays( 1 ).atTime( OffsetTime.MIN ) ;

odtStart.toString () = 2020-01-30T00: 00 + 18: 00

odtStop.toString () = 2020-01-31T00: 00 + 18: 00

Ces OffsetDateTime objets seront déjà en UTC, mais vous pouvez appeler toInstantsi vous avez besoin de tels objets qui sont toujours en UTC par définition.

Instant start = odtStart.toInstant() ;
Instant stop = odtStop.toInstant() ;

start.toString () = 2020-01-29T06: 00: 00Z

stop.toString () = 2020-01-30T06: 00: 00Z

Astuce: Vous pouvez être intéressé par l'ajout de la bibliothèque ThreeTen-Extra à votre projet pour utiliser sa Intervalclasse pour représenter cette paire d' Instantobjets. Cette catégorie offre des méthodes utiles aux fins de comparaison tels que abuts, overlaps, containset plus encore.

Interval interval = Interval.of( start , stop ) ;

interval.toString () = 2020-01-29T06: 00: 00Z / 2020-01-30T06: 00: 00Z

À moitié ouvert

La réponse de mprivat est correcte. Son point est de ne pas essayer d'obtenir la fin d'une journée, mais plutôt de comparer à "avant le début du lendemain". Son idée est connue sous le nom d'approche «semi-ouverte» où une période de temps a un début inclusif tandis que la fin est exclusive .

  • Les frameworks date-heure actuels de Java (java.util.Date/Calendar et Joda-Time ) utilisent tous les deux des millisecondes de l' époque . Mais dans Java 8, les nouvelles classes JSR 310 java.time. * Utilisent une résolution en nanosecondes. Tout code que vous avez écrit en forçant le décompte des millisecondes du dernier moment de la journée serait incorrect s'il passait aux nouvelles classes.
  • La comparaison des données d'autres sources devient erronée si elles utilisent d'autres résolutions. Par exemple, les bibliothèques Unix utilisent généralement des secondes entières et les bases de données telles que Postgres résolvent la date-heure en microsecondes.
  • Certains changements d'heure d'été se produisent pendant minuit, ce qui peut encore compliquer les choses.

entrez la description de l'image ici

Joda-Time 2.3 offre une méthode à cet effet, pour obtenir très premier moment de la journée: withTimeAtStartOfDay(). De même dans java.time, LocalDate::atStartOfDay.

Recherchez dans StackOverflow "joda semi-ouvert" pour voir plus de discussions et d'exemples.

Voir cet article, les intervalles de temps et autres plages devraient être à moitié ouverts , par Bill Schneider.

Évitez les anciennes classes date-heure

Les classes java.util.Date et .Calendar sont notoirement gênantes. Évite-les.

Utilisez les classes java.time . Le framework java.time est le successeur officiel du très réussi Joda-Time bibliothèque .

java.time

Le framework java.time est intégré à Java 8 et versions ultérieures. Rétroporté vers Java 6 et 7 dans le projet ThreeTen-Backport , ensuite adapté à Android dans le ThreeTenABP projet .

An Instantest un moment sur la chronologie en UTC avec une résolution de nanosecondes .

Instant instant = Instant.now();

Appliquer un fuseau horaire pour obtenir l' heure de l'horloge murale pour une localité.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

Pour obtenir le premier moment de la journée, parcourez la LocalDateclasse et sonatStartOfDay méthode.

ZonedDateTime zdtStart = zdt.toLocalDate().atStartOfDay( zoneId );

En utilisant l'approche semi-ouverte, obtenez le premier moment du jour suivant.

ZonedDateTime zdtTomorrowStart = zdtStart.plusDays( 1 );

Tableau de tous les types de date-heure en Java, à la fois modernes et hérités

Actuellement, le framework java.time n'a pas de Intervalclasse comme décrit ci-dessous pour Joda-Time. Cependant, le projet ThreeTen-Extra étend java.time avec des classes supplémentaires. Ce projet est le terrain d'essai pour d'éventuels futurs ajouts à java.time. Parmi ses classes est Interval. Construisez un Intervalen passant une paire d' Instantobjets. Nous pouvons extraire un Instantde nos ZonedDateTimeobjets.

Interval today = Interval.of( zdtStart.toInstant() , zdtTomorrowStart.toInstant() );

À propos de java.time

Le framework java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciens gênants hérités des classes date-heure tels que java.util.Date, Calendar, et SimpleDateFormat.

Pour en savoir plus, consultez le didacticiel Oracle . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 310 .

Le projet Joda-Time , désormais en mode maintenance , conseille la migration vers les classes java.time .

Vous pouvez échanger des objets java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou version ultérieure. Pas besoin de chaînes, pas besoin de java.sql.*classes. Prise en charge d'Hibernate 5 et JPA 2.2 java.time .

Où obtenir les classes java.time?


Joda-Time

MISE À JOUR: Le projet Joda-Time est maintenant en mode maintenance et conseille la migration vers les classes java.time . Je laisse cette section intacte pour l'histoire.

Joda-Time a trois classes pour représenter un laps de temps de diverses manières: Interval, Periodet Duration. An Intervala un début et une fin spécifiques sur la chronologie de l'Univers. Cela correspond à notre besoin de représenter «un jour».

Nous appelons la méthode withTimeAtStartOfDayplutôt que de définir l'heure de la journée sur des zéros. En raison de l'heure d'été et d'autres anomalies, le premier moment de la journée peut ne pas être00:00:00 .

Exemple de code utilisant Joda-Time 2.3.

DateTimeZone timeZone = DateTimeZone.forID( "America/Montreal" );
DateTime now = DateTime.now( timeZone );
DateTime todayStart = now.withTimeAtStartOfDay();
DateTime tomorrowStart = now.plusDays( 1 ).withTimeAtStartOfDay();
Interval today = new Interval( todayStart, tomorrowStart );

Si vous le devez, vous pouvez le convertir en java.util.Date.

java.util.Date date = todayStart.toDate();
Basil Bourque
la source
J'ai besoin d'utiliserLocalDateTime
user924 le
170

Java 8


public static Date atStartOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
    return localDateTimeToDate(startOfDay);
}

public static Date atEndOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
    return localDateTimeToDate(endOfDay);
}

private static LocalDateTime dateToLocalDateTime(Date date) {
    return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}

private static Date localDateTimeToDate(LocalDateTime localDateTime) {
    return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}

Mise à jour : j'ai ajouté ces 2 méthodes à mes classes d'utilitaires Java ici

Il se trouve dans le référentiel central de Maven à:

<dependency>
  <groupId>com.github.rkumsher</groupId>
  <artifactId>utils</artifactId>
  <version>1.3</version>
</dependency>

Java 7 et versions antérieures


Avec Apache Commons

public static Date atEndOfDay(Date date) {
    return DateUtils.addMilliseconds(DateUtils.ceiling(date, Calendar.DATE), -1);
}

public static Date atStartOfDay(Date date) {
    return DateUtils.truncate(date, Calendar.DATE);
}

Sans Apache Commons

public Date atEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 59);
    calendar.set(Calendar.SECOND, 59);
    calendar.set(Calendar.MILLISECOND, 999);
    return calendar.getTime();
}

public Date atStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar.getTime();
}
RKumsher
la source
5
Je déconseille cette approche en raison des problèmes de cas extrêmes que vous rencontrerez avec les fuseaux horaires, l'heure d'été, etc. qu'une bibliothèque de temps comme Joda prendra en charge pour vous.
Edward Anderson
7
Je pense que dans getStartOfDaydevrait être: LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);Ie LocalTime, pas LocalDateTime.
Konstantin Kulagin
Ne pensez pas ça getEndOfDay(Date date). getStartOfDay(Date date)les méthodes de la section Java 8 sont correctes, à savoir LocalDateTime.MAX et .MIN vous placent aux dates les plus précoces possible dans le passé et dans le futur, pas au début et à la fin d'une journée. Au lieu de cela, je suggérerais .atStartOfDay () pour le début et .plusDays (1) .atStartOfDay (). MinusNanos (1) pour la fin.
Glen Mazza
La méthode localDateTimeToDate(LocalDateTime startOfDay)lève une exception - java.lang.IllegalArgumentException: java.lang.ArithmeticException: long overflow
sanluck
1
@GlenMazza la solution Java 8 utilise LocalTime .MAX , pas LocalDateTime.MAX . La solution est correcte.
Frederico Pantuzza
28

dans getEndOfDay, vous pouvez ajouter:

calendar.set(Calendar.MILLISECOND, 999);

Bien que mathématiquement parlant, vous ne pouvez pas spécifier la fin d'une journée autrement qu'en disant que c'est "avant le début du jour suivant".

Ainsi , au lieu de dire if(date >= getStartOfDay(today) && date <= getEndOfDay(today)), vous devriez dire: if(date >= getStartOfDay(today) && date < getStartOfDay(tomorrow)). C'est une définition beaucoup plus solide (et vous n'avez pas à vous soucier de la précision à la milliseconde).

mprivat
la source
1
La seconde moitié de cette réponse est le meilleur moyen. Cette approche est connue sous le nom de «semi-ouverte». J'explique plus loin dans ma réponse .
Basil Bourque
14

java.time

Utilisation du java.timeframework intégré à Java 8.

import java.time.LocalTime;
import java.time.LocalDateTime;

LocalDateTime now = LocalDateTime.now(); // 2015-11-19T19:42:19.224
// start of a day
now.with(LocalTime.MIN); // 2015-11-19T00:00
now.with(LocalTime.MIDNIGHT); // 2015-11-19T00:00
// end of a day
now.with(LocalTime.MAX); // 2015-11-19T23:59:59.999999999
Przemek
la source
3
Cette réponse ignore les anomalies telles que l'heure d'été (DST). Les Local…classes n'ont aucun concept de fuseaux horaires et d'ajustement pour les anomalies. Par exemple, certains fuseaux horaires ont un changement d'heure d'été à minuit, de sorte que le premier moment de la journée est à l'heure 01:00:00.0(1 AM) plutôt qu'à la 00:00:00.0valeur produite par ce code. Utilisez ZonedDateTimeplutôt.
Basil Bourque
4

Un autre moyen de trouver le début de la journée avec java8 java.time.ZonedDateTime au lieu de le parcourir LocalDateTimeest simplement de tronquer l'entrée ZonedDateTime en DAYS:

zonedDateTimeInstance.truncatedTo( ChronoUnit.DAYS );
laur
la source
2

Pour java 8, les instructions sur une seule ligne suivantes fonctionnent. Dans cet exemple, j'utilise le fuseau horaire UTC. Veuillez envisager de modifier la zone horaire que vous utilisez actuellement.

System.out.println(new Date());

final LocalDateTime endOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
final Date          endOfDayAsDate = Date.from(endOfDay.toInstant(ZoneOffset.UTC));

System.out.println(endOfDayAsDate);

final LocalDateTime startOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
final Date          startOfDayAsDate = Date.from(startOfDay.toInstant(ZoneOffset.UTC));

System.out.println(startOfDayAsDate);

Si aucun décalage horaire avec la sortie. Essayer:ZoneOffset.ofHours(0)

Fırat KÜÇÜK
la source
1
Comment cela ajoute-t-il de la valeur au-delà de celle des réponses existantes? En outre, vous ignorez la question cruciale du fuseau horaire. Et passer la constante UTC à toInstantest une syntaxe illégale et redondante sur le plan conceptuel.
Basil Bourque
@BasilBourque "Comment cela ajoute-t-il de la valeur au-delà de celle des réponses existantes?" C'est ainsi que fonctionne le stackoverflow. BTW, ce n'est qu'un modèle. Les gens peuvent changer comme ils le souhaitent. passer UTC à toInstant est totalement légal, vous pouvez examiner les sorties.
Fırat KÜÇÜK
2

Java 8 ou ThreeTenABP

ZonéDateHeure

ZonedDateTime curDate = ZonedDateTime.now();

public ZonedDateTime startOfDay() {
    return curDate
    .toLocalDate()
    .atStartOfDay()
    .atZone(curDate.getZone())
    .withEarlierOffsetAtOverlap();
}

public ZonedDateTime endOfDay() {

    ZonedDateTime startOfTomorrow =
        curDate
        .toLocalDate()
        .plusDays(1)
        .atStartOfDay()
        .atZone(curDate.getZone())
        .withEarlierOffsetAtOverlap();

    return startOfTomorrow.minusSeconds(1);
}

// based on https://stackoverflow.com/a/29145886/1658268

LocalDateTime

LocalDateTime curDate = LocalDateTime.now();

public LocalDateTime startOfDay() {
    return curDate.atStartOfDay();
}

public LocalDateTime endOfDay() {
    return startOfTomorrow.atTime(LocalTime.MAX);  //23:59:59.999999999;
}

// based on https://stackoverflow.com/a/36408726/1658268

J'espère que cela aide quelqu'un.

SudoPlz
la source
La méthode "atTime" est une très bonne approche avec la constante "LocalTime.MAX" pour la fin de la journée. Agréable!
M. Anderson
2

J'ai essayé ce code et ça marche bien!

final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
final ZonedDateTime startofDay =
    now.toLocalDate().atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime endOfDay =
    now.toLocalDate().atTime(LocalTime.MAX).atZone(ZoneOffset.UTC);
Siddhey Sankhe
la source
Si vous avez ZonedDateTimeet Instantà votre disposition, il n'est jamais nécessaire de l'utiliser java.sql.Timestamp. Cette classe est l'une des terribles anciennes classes héritées désormais supplantées par les classes java.time . Depuis JDBC 4.2, nous pouvons directement échanger des objets java.time avec la base de données. Votre mélange de l'héritage et des cours modernes n'a aucun sens pour moi.
Basil Bourque
1

Une autre solution qui ne dépend d'aucun framework est:

static public Date getStartOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date(day.getTime() / oneDayInMillis * oneDayInMillis);
}

static public Date getEndOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date((day.getTime() / oneDayInMillis + 1) * oneDayInMillis - 1);
}

Notez qu'il renvoie l'heure UTC

Alexandre Chingarev
la source
1
private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
    }

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
    }

calendar.setTimeInMillis (0); vous donne une précision allant jusqu'à millisecondes

mannequin venkat
la source
1
Ces terribles classes de date-heure ont été il y a des années remplacées par les classes java.time modernes définies dans JSR 310.
Basil Bourque
@BasilBourque le package java.time n'est apparu que dans Java 8 qui n'est disponible que depuis Android N (API 26)
ivan8m8 le
@ ivan8m8 La plupart des fonctionnalités java.time sont rétroportées vers Java 6 et 7 dans la bibliothèque ThreeTen-Backport . Adapté pour les premiers Android (<26) dans la bibliothèque ThreeTenABP .
Basil Bourque le
@BasilBourque mais le ThreeTen utilise un mécanisme extrêmement inefficace sur Android .
ivan8m8 le
@ ivan8m8 Vous pensez peut-être au prédécesseur de java.time , Joda-Time . Encore une fois, consultez le projet ThreeTenABP . Je n'ai pas entendu parler d'une telle inefficacité sur Android en utilisant cette bibliothèque.
Basil Bourque le
0

Je sais que c'est un peu tard, mais dans le cas de Java 8, si vous utilisez OffsetDateTime (qui offre de nombreux avantages, tels que TimeZone, Nanosecondes, etc.), vous pouvez utiliser le code suivant:

OffsetDateTime reallyEndOfDay = someDay.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
// output: 2019-01-10T23:59:59.999999999Z
Florin
la source
0

J'ai eu plusieurs inconvénients avec toutes les solutions car j'avais besoin du type de variable instantanée et le fuseau horaire interférait toujours en changeant tout, puis en combinant les solutions, j'ai vu que c'était une bonne option.

        LocalDate today = LocalDate.now();
        Instant startDate = Instant.parse(today.toString()+"T00:00:00Z");
        Instant endDate = Instant.parse(today.toString()+"T23:59:59Z");

et nous avons en conséquence

        startDate = 2020-01-30T00:00:00Z
        endDate = 2020-01-30T23:59:59Z

J'espère que ça t'aide

yOshi
la source
La journée se termine en fait à l'arrivée du premier moment du suivant jour. Votre code saute la dernière seconde complète d'une journée, un milliard de nanosecondes qui ne sont jamais signalées. Consultez ma réponse pour en savoir plus sur l'approche semi-ouverte pour définir un intervalle de temps.
Basil Bourque le
La manipulation de chaînes comme moyen de gérer les valeurs date-heure est généralement une mauvaise approche. Utilisez des objets intelligents plutôt que des chaînes stupides pour ce travail. Les classes java.time offrent toutes les fonctionnalités dont vous avez besoin pour résoudre ce problème sans recourir à la manipulation de chaînes.
Basil Bourque le
Je viens d'ajouter une section tl; dr à ma réponse montrant comment obtenir la portée d'une journée sous forme de paire d' Instantobjets. Astuce: vous pourriez être intéressé par l'ajout de la bibliothèque ThreeTen-Extra à votre projet pour utiliser sa Intervalclasse.
Basil Bourque le
0

Je pense que le plus simple serait quelque chose comme:

// Joda Time

DateTime dateTime=new DateTime(); 

StartOfDayMillis = dateTime.withMillis(System.currentTimeMillis()).withTimeAtStartOfDay().getMillis();
EndOfDayMillis = dateTime.withMillis(StartOfDayMillis).plusDays(1).minusSeconds(1).getMillis();

Ces millis peuvent ensuite être convertis en Calendar, Instant ou LocalDate selon vos besoins avec Joda Time.

virus heureux
la source
-2
public static Date beginOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);

    return cal.getTime();
}

public static Date endOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 23);
    cal.set(Calendar.MINUTE, 59);
    cal.set(Calendar.SECOND, 59);
    cal.set(Calendar.MILLISECOND, 999);

    return cal.getTime();
}
jack jin
la source