Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0452211
feat: implement Error for ParseError
sunng87 Jan 21, 2026
4f20ffb
style: format code
sunng87 Jan 21, 2026
6ea2eba
style: format
sunng87 Jan 21, 2026
3bfab3e
fix: pluralization for postgres style
sunng87 Jan 21, 2026
db10de0
fix: several roundtrip cases
sunng87 Jan 21, 2026
81359a9
feat: implement postgres_verbose
sunng87 Jan 21, 2026
c89c533
ci: add github actions
sunng87 Jan 21, 2026
4843bb1
ci: update ci
sunng87 Jan 21, 2026
eec5c4d
ci: update arm runner
sunng87 Jan 21, 2026
d3cba01
feat: add parse_sql for interval
sunng87 Jan 21, 2026
aa519d5
Merge pull request #4 from sunng87/feature/parse-sql
sunng87 Jan 21, 2026
1c26cd2
Merge pull request #3 from sunng87/feature/postgres-verbose
sunng87 Jan 21, 2026
95d9f4b
Merge pull request #2 from sunng87/feature/parse-error
sunng87 Jan 21, 2026
9a1c365
Merge branch 'master' into feature/postgres-style
sunng87 Jan 21, 2026
17ca774
fix: format
sunng87 Jan 21, 2026
7e6ea8f
Merge pull request #1 from sunng87/feature/postgres-style
sunng87 Jan 21, 2026
29f6436
feat: from_postgres_verbose
sunng87 Jan 21, 2026
9e217a6
Merge pull request #5 from sunng87/feature/from-postgres-verbose
sunng87 Jan 21, 2026
bb2de2a
fix: iso format
sunng87 Jan 22, 2026
0f8d14c
fix: sql standard format for leading zero
sunng87 Jan 22, 2026
2192fad
fix: sql 0 output
sunng87 Jan 22, 2026
bd20f1c
fix: update pluar form of fractional sections
sunng87 Jan 22, 2026
18a56d2
Merge pull request #6 from sunng87/fix/iso-format
sunng87 Jan 22, 2026
6f582fb
chore: fork
sunng87 Jan 22, 2026
b8499f7
chore: Release pg_interval_2 version 0.5.1
sunng87 Jan 22, 2026
016e12a
fix: correct to_sql format
sunng87 Jan 23, 2026
541247a
Merge pull request #7 from sunng87/fix/sql_standard
sunng87 Jan 23, 2026
ee6917c
chore: Release pg_interval_2 version 0.5.2
sunng87 Jan 23, 2026
53b6f86
Merge branch 'master' into master
sunng87 Feb 5, 2026
a1fb277
Update README.md
sunng87 Feb 5, 2026
2f9996d
Update Cargo.toml
sunng87 Feb 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 35 additions & 50 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,42 @@ env:

jobs:
test:
name: Build, Lint, and Test
runs-on: ubuntu-latest
container:
image: rust:alpine
steps:
- name: Install Alpine dependencies
run: apk add --no-cache git musl-dev

- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust components
run: rustup component add clippy rustfmt
name: test - ${{ matrix.target }}
strategy:
fail-fast: true
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
os: ubuntu
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
os: ubuntu
- target: aarch64-apple-darwin
runner: macos-latest
os: macos
runs-on: ${{ matrix.runner }}

- name: Cache Cargo dependencies
uses: actions/cache@v3
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-alpine-cargo-${{ hashFiles('**/Cargo.lock') }}

- name: Check formatting
run: cargo fmt -- --check

- name: Lint with Clippy
run: cargo clippy -- -D warnings

- name: Run Tests
run: cargo test --verbose

publish:
name: Publish to Crates.io
target: ${{ matrix.target }}
rustflags: ""
- name: run test
run: |
cargo test --verbose

# Check formatting with rustfmt
formatting:
name: cargo fmt
runs-on: ubuntu-latest
needs: test
environment: master
if: github.ref == 'refs/heads/master'
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Build Release
run: cargo build --release

- name: Publish to Crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --token $CARGO_REGISTRY_TOKEN
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this action to publish every commit from master branch?

Copy link
Owner

@piperRyan piperRyan Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @sunng87 yes it was/is. On a side note I do have a local branch that I was planing to get merged in to switch it to manual approval + cargo smart release if you want that, but how do you want to CD part to work? I can help get that part done.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we trigger the cargo publish when a tag is pushed.

see this for example: https://github.com/GreptimeTeam/promql-parser/blob/main/.github/workflows/release.yml

- uses: actions/checkout@v4
# Ensure rustfmt is installed and setup problem matcher
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: rustfmt
rustflags: ""
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "pg_interval"
version = "0.4.3"
edition = "2018"
authors = ["Ryan Piper <piper.ryan235@gmail.com>"]
authors = ["Ryan Piper <piper.ryan235@gmail.com>", "Ning Sun <n@sunng.info>"]
license = "MIT"
description = "A native PostgreSQL interval type"
repository = "https://github.com/piperRyan/rust-postgres-interval"
Expand Down
17 changes: 0 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
[![Build Status](https://travis-ci.org/piperRyan/rust-postgres-interval.svg?branch=master)](https://travis-ci.org/piperRyan/rust-postgres-interval) [![codecov](https://codecov.io/gh/piperRyan/rust-postgres-interval/branch/master/graph/badge.svg)](https://codecov.io/gh/piperRyan/rust-postgres-interval)

# Rust-Postgres-Interval
A interval type for the postgres driver.

Expand All @@ -23,18 +21,3 @@ fn main() {
assert_eq!(String::from("P1Y1M1DT1H"), output);
}
```

## Requirements
- rust 1.22

## Roadmap to 1.0.0

- [x] Convert Interval Into Formated String
- [x] Iso 8601
- [x] Postgres
- [x] Sql
- [ ] Parse Formated Strings Into The Interval Type
- [x] Iso 8601
- [x] Postgres
- [ ] Sql
- [x] Chrono Integrations
4 changes: 2 additions & 2 deletions src/integrations/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ impl Interval {
let mut days = duration.num_days();
let mut new_dur = duration - Duration::days(days);
let mut hours = duration.num_hours();
new_dur -= Duration::hours(hours);
new_dur = new_dur - Duration::hours(hours);
let minutes = new_dur.num_minutes();
new_dur -= Duration::minutes(minutes);
new_dur = new_dur - Duration::minutes(minutes);
let nano_secs = new_dur.num_nanoseconds()?;
if days > (i32::MAX as i64) {
let overflow_days = days - (i32::MAX as i64);
Expand Down
19 changes: 13 additions & 6 deletions src/interval_fmt/iso_8601.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@ impl IntervalNorm {
if self.minutes != 0 {
time_interval.push_str(&format!("{}M", self.minutes));
}
if self.seconds != 0 {
time_interval.push_str(&format!("{}S", self.seconds));
}
if self.microseconds != 0 {
let ms = super::safe_abs_u64(self.microseconds);
time_interval.push_str(&format!(".{:06}", ms));
if self.seconds != 0 || self.microseconds != 0 {
let secs_with_micros = if self.seconds != 0 && self.microseconds != 0 {
format!(
"{}.{:06}",
self.seconds,
super::safe_abs_u64(self.microseconds)
)
} else if self.microseconds != 0 {
format!(".{:06}", super::safe_abs_u64(self.microseconds))
} else {
format!("{}", self.seconds)
};
time_interval.push_str(&format!("{}S", secs_with_micros));
}
} else {
time_interval = "".to_owned();
Expand Down
151 changes: 148 additions & 3 deletions src/interval_fmt/postgres.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
use crate::interval_norm::IntervalNorm;

fn get_year_suffix(value: i32) -> &'static str {
if value == 1 {
"year"
} else {
"years"
}
}

fn get_mon_suffix(value: i32) -> &'static str {
if value == 1 {
"mon"
} else {
"mons"
}
}

fn get_day_suffix(value: i32) -> &'static str {
if value == 1 {
"day"
} else {
"days"
}
}

fn get_hour_suffix(value: i64) -> &'static str {
if value == 1 {
"hour"
} else {
"hours"
}
}

fn get_min_suffix(value: i64) -> &'static str {
if value == 1 {
"min"
} else {
"mins"
}
}

fn get_sec_suffix(seconds: i64, microseconds: i64) -> &'static str {
if seconds == 1 && microseconds == 0 {
"sec"
} else {
"secs"
}
}

impl IntervalNorm {
/// Produces a postgres compliant interval string.
pub fn into_postgres(self) -> String {
Expand All @@ -10,14 +58,18 @@ impl IntervalNorm {
let mut day_interval = "".to_owned();
let time_interval = self.get_postgres_time_interval();
if self.is_day_present() {
day_interval = format!("{:#?} days ", self.days)
day_interval = format!("{} {} ", self.days, get_day_suffix(self.days))
}
if self.is_year_month_present() {
if self.years != 0 {
year_interval.push_str(&format!("{:#?} year ", self.years))
year_interval.push_str(&*format!("{} {} ", self.years, get_year_suffix(self.years)))
}
if self.months != 0 {
year_interval.push_str(&format!("{:#?} mons ", self.months));
year_interval.push_str(&*format!(
"{} {} ",
self.months,
get_mon_suffix(self.months)
));
}
}
year_interval.push_str(&day_interval);
Expand Down Expand Up @@ -48,4 +100,97 @@ impl IntervalNorm {
}
time_interval
}

/// Produces a postgres_verbose compliant interval string.
pub fn into_postgres_verbose(self) -> String {
let is_negative = !self.is_time_interval_pos()
&& (self.years < 0
|| self.months < 0
|| self.days < 0
|| self.hours < 0
|| self.minutes < 0
|| self.seconds < 0
|| self.microseconds < 0);

let mut parts = Vec::new();

if self.years != 0 {
let abs_years = if self.years < 0 {
-self.years
} else {
self.years
};
parts.push(format!("{} {}", abs_years, get_year_suffix(abs_years)));
}

if self.months != 0 {
let abs_months = if self.months < 0 {
-self.months
} else {
self.months
};
parts.push(format!("{} {}", abs_months, get_mon_suffix(abs_months)));
}

if self.days != 0 {
let abs_days = if self.days < 0 { -self.days } else { self.days };
parts.push(format!("{} {}", abs_days, get_day_suffix(abs_days)));
}

if self.hours != 0 {
let abs_hours = if self.hours < 0 {
-self.hours
} else {
self.hours
};
parts.push(format!("{} {}", abs_hours, get_hour_suffix(abs_hours)));
}

if self.minutes != 0 {
let abs_minutes = if self.minutes < 0 {
-self.minutes
} else {
self.minutes
};
parts.push(format!("{} {}", abs_minutes, get_min_suffix(abs_minutes)));
}

if self.seconds != 0 || self.microseconds != 0 {
let abs_seconds = if self.seconds < 0 {
-self.seconds
} else {
self.seconds
};
let abs_micros = if self.microseconds < 0 {
-self.microseconds
} else {
self.microseconds
};
if abs_micros != 0 {
let secs_with_micros = abs_seconds as f64 + abs_micros as f64 / 1_000_000.0;
parts.push(format!(
"{} {}",
secs_with_micros,
get_sec_suffix(abs_seconds, abs_micros)
));
} else {
parts.push(format!(
"{} {}",
abs_seconds,
get_sec_suffix(abs_seconds, abs_micros)
));
}
}

if parts.is_empty() {
return "@ 0".to_owned();
}

let result = format!("@ {}", parts.join(" "));
if is_negative {
format!("{} ago", result)
} else {
result
}
}
}
Loading